Browse Source

merge V20220228

tags/V1.22.3.1^2
liuzx 4 years ago
parent
commit
36a67b1c1b
51 changed files with 1357 additions and 125 deletions
  1. +1
    -0
      models/models.go
  2. +9
    -0
      models/user.go
  3. +98
    -0
      models/wechat_bind.go
  4. +67
    -0
      modules/auth/wechat/access_token.go
  5. +71
    -0
      modules/auth/wechat/bind.go
  6. +5
    -0
      modules/auth/wechat/call.go
  7. +126
    -0
      modules/auth/wechat/client.go
  8. +76
    -0
      modules/auth/wechat/event_handle.go
  9. +13
    -0
      modules/auth/wechat/qr_code.go
  10. +38
    -6
      modules/context/auth.go
  11. +15
    -0
      modules/context/context.go
  12. +1
    -0
      modules/modelarts/resty.go
  13. +87
    -0
      modules/redis/redis_client/client.go
  14. +16
    -0
      modules/redis/redis_key/key_base.go
  15. +14
    -0
      modules/redis/redis_key/wechat_redis_key.go
  16. +40
    -0
      modules/redis/redis_lock/lock.go
  17. +17
    -1
      modules/setting/setting.go
  18. +9
    -0
      options/locale/locale_en-US.ini
  19. +9
    -0
      options/locale/locale_zh-CN.ini
  20. +10
    -0
      package-lock.json
  21. +2
    -0
      package.json
  22. +13
    -5
      public/home/home.js
  23. BIN
      public/img/qrcode_reload.png
  24. +7
    -6
      routers/admin/cloudbrains.go
  25. +9
    -3
      routers/api/v1/api.go
  26. +4
    -7
      routers/api/v1/repo/cloudbrain.go
  27. +126
    -0
      routers/authentication/wechat.go
  28. +47
    -0
      routers/authentication/wechat_event.go
  29. +2
    -0
      routers/repo/cloudbrain.go
  30. +8
    -1
      routers/repo/modelarts.go
  31. +23
    -13
      routers/routes/routes.go
  32. +1
    -1
      services/socketwrap/clientManager.go
  33. +102
    -6
      templates/admin/cloudbrain/list.tmpl
  34. +14
    -10
      templates/admin/cloudbrain/search.tmpl
  35. +4
    -1
      templates/repo/cloudbrain/benchmark/new.tmpl
  36. +1
    -1
      templates/repo/cloudbrain/new.tmpl
  37. +1
    -2
      templates/repo/cloudbrain/show.tmpl
  38. +4
    -2
      templates/repo/debugjob/index.tmpl
  39. +0
    -34
      templates/repo/issue/new_form.tmpl
  40. +3
    -3
      templates/repo/issue/view_content/pull.tmpl
  41. +2
    -2
      templates/repo/modelarts/inferencejob/new.tmpl
  42. +2
    -5
      templates/repo/modelarts/notebook/new.tmpl
  43. +1
    -1
      templates/repo/modelarts/notebook/show.tmpl
  44. +1
    -4
      templates/repo/modelarts/trainjob/show.tmpl
  45. +15
    -0
      templates/repo/wx_autorize.tmpl
  46. +66
    -0
      templates/user/settings/profile.tmpl
  47. +1
    -1
      web_src/js/components/MinioUploader.vue
  48. +1
    -1
      web_src/js/components/ObsUploader.vue
  49. +150
    -0
      web_src/js/components/WxAutorize.vue
  50. +8
    -5
      web_src/js/features/cloudrbanin.js
  51. +17
    -4
      web_src/js/index.js

+ 1
- 0
models/models.go View File

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

tablesStatistic = append(tablesStatistic,


+ 9
- 0
models/user.go View File

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

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

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

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

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


+ 98
- 0
models/wechat_bind.go View File

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

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

type WechatBindAction int

const (
WECHAT_BIND WechatBindAction = iota + 1
WECHAT_UNBIND
)

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

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

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

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

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

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

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

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

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

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

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

const EMPTY_REDIS_VAL = "Nil"

var accessTokenLock = redis_lock.NewDistributeLock()

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

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

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

}

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

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

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

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

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

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

type QRCode4BindCache struct {
UserId int64
Status int
}

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

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

type WechatBindError struct {
Reply string
}

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

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

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

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

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

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

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

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

type WechatCall interface {
call()
}

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

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

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

var (
client *resty.Client
)

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

ERR_CODE_ACCESSTOKEN_EXPIRE = 42001
ERR_CODE_ACCESSTOKEN_INVALID = 40001
)

type AccessTokenResponse struct {
Access_token string
Expires_in int
}

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

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

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

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

type ErrorResponse struct {
Errcode int
Errmsg string
}

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

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

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

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

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

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

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

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

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

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

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

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

const (
WECHAT_MSG_TYPE_TEXT = "text"
)

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

return BIND_REPLY_SUCCESS
}

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

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

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

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

+ 38
- 6
modules/context/auth.go View File

@@ -23,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
@@ -135,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 {


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

@@ -354,3 +354,18 @@ func Contexter() macaron.Handler {
c.Map(ctx)
}
}

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

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

@@ -35,6 +35,7 @@ const (
//error code
modelartsIllegalToken = "ModelArts.6401"
NotebookNotFound = "ModelArts.6404"
NotebookNoPermission = "ModelArts.6403"
)

func getRestyClient() *resty.Client {


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

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

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

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

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

}

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

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

}

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

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

}

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

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

}

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

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

}

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

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

import "strings"

const KEY_SEPARATE = ":"

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

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

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

const PREFIX = "wechat"

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

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

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

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

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

type DistributeLock struct {
}

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

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

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

return false
}

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

+ 17
- 1
modules/setting/setting.go View File

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

//snn4imagenet config
IsSnn4imagenetEnabled bool
@@ -531,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 {
@@ -1345,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")


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

@@ -493,6 +493,14 @@ account_link = Linked Accounts
organization = Organizations
uid = Uid
u2f = Security Keys
bind_wechat = Bind WeChat
wechat_bind = WeChat Binding
bind_account_information = Bind account information
bind_time = Bind Time
wechat = Wechat
unbind_wc = Unbind
unbind_wechat = Are you sure you want to unbind WeChat?
unbind_computing = After unbundling, the qizhi computing power environment will not be available

public_profile = Public Profile
profile_desc = Your email address will be used for notifications and other operations.
@@ -2353,6 +2361,7 @@ cloudbrain.all_computing_resources=All Computing Resources
cloudbrain.all_status=All Status
cloudbrain.download_report=Download Report
cloudbrain.cloudbrain_name=Cloudbrain Name
cloudbrain.search = Seach Task Name/Creter

hooks.desc = Webhooks automatically make HTTP POST requests to a server when certain openi events trigger. Webhooks defined here are defaults and will be copied into all new repositories. Read more in the <a target="_blank" rel="noopener" href="https://docs.openi.io/en-us/webhooks/">webhooks guide</a>.
hooks.add_webhook = Add Default Webhook


+ 9
- 0
options/locale/locale_zh-CN.ini View File

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

public_profile=公开信息
profile_desc=您的电子邮件地址将用于通知和其他操作。
@@ -2362,6 +2370,7 @@ cloudbrain.all_computing_resources=全部计算资源
cloudbrain.all_status=全部状态
cloudbrain.download_report=下载此报告
cloudbrain.cloudbrain_name=云脑侧名称
cloudbrain.search = 搜索任务名称/创建者

hooks.desc=当某些 openi 事件触发时, Web 钩子会自动向服务器发出 HTTP POST 请求。此处定义的 Web 钩子是默认值, 将复制到所有新建项目中。参阅 <a target="_blank" rel="noopener" href="https://docs.openi.io/en-us/webhooks/">Web钩子指南</a> 获取更多内容。
hooks.add_webhook=新增默认Web钩子


+ 10
- 0
package-lock.json View File

@@ -7787,6 +7787,11 @@
}
}
},
"js-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz",
"integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw=="
},
"js-file-download": {
"version": "0.4.12",
"resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz",
@@ -11147,6 +11152,11 @@
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
},
"qrcodejs2": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/qrcodejs2/-/qrcodejs2-0.0.2.tgz",
"integrity": "sha1-Rlr+Xjnxn6zsuTLBH3oYYQkUauE="
},
"qs": {
"version": "6.9.4",
"resolved": "https://registry.npm.taobao.org/qs/download/qs-6.9.4.tgz",


+ 2
- 0
package.json View File

@@ -33,6 +33,7 @@
"jquery": "3.5.1",
"jquery-datetimepicker": "2.5.21",
"jquery.are-you-sure": "1.9.0",
"js-cookie": "3.0.1",
"less-loader": "6.1.0",
"mini-css-extract-plugin": "0.9.0",
"monaco-editor": "0.20.0",
@@ -41,6 +42,7 @@
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.2",
"qrcodejs2": "0.0.2",
"qs": "6.9.4",
"remixicon": "2.5.0",
"spark-md5": "3.0.1",


+ 13
- 5
public/home/home.js View File

@@ -94,10 +94,12 @@ socket.onmessage = function (e) {
var currentTime = new Date().getTime();
for(var i = 0; i < messageQueue.length; i++){
var record = messageQueue[i];

var recordPrefix = getMsg(record);
var actionName = getAction(record.OpType,isZh);

if(record.ActUser == null){
console.log("receive action type=" + record.OpType + " name=" + actionName + " but user is null.");
continue;
}
var recordPrefix = getMsg(record);
if(record.OpType == "6" || record.OpType == "10" || record.OpType == "12" || record.OpType == "13"){
html += recordPrefix + actionName;
html += " <a href=\"" + getIssueLink(record) + "\" rel=\"nofollow\">" + getIssueText(record) + "</a>"
@@ -170,9 +172,15 @@ function getTaskLink(record){
function getMsg(record){
var html ="";
html += "<div class=\"swiper-slide item\">";
html += " <img class=\"ui avatar image\" src=\"/user/avatar/" + record.ActUser.Name + "/-1\" alt=\"\">"
var name = "";
if(record.ActUser != null){
name = record.ActUser.Name;
}else{
console.log("act user is null.");
}
html += " <img class=\"ui avatar image\" src=\"/user/avatar/" + name + "/-1\" alt=\"\">"
html += " <div class=\"middle aligned content nowrap\">"
html += " <a href=\"/" + record.ActUser.Name + "\" title=\"\">" + record.ActUser.Name + "</a>"
html += " <a href=\"/" + name + "\" title=\"\">" + name + "</a>"
return html;
}



BIN
public/img/qrcode_reload.png View File

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

+ 7
- 6
routers/admin/cloudbrains.go View File

@@ -21,7 +21,7 @@ import (
const (
tplCloudBrains base.TplName = "admin/cloudbrain/list"
EXCEL_DATE_FORMAT = "20060102150405"
CREATE_TIME_FORMAT = "2006/01/02 15:04:05.00"
CREATE_TIME_FORMAT = "2006/01/02 15:04:05"
)

func CloudBrains(ctx *context.Context) {
@@ -62,7 +62,7 @@ func CloudBrains(ctx *context.Context) {
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.ModelArtsStopping), string(models.ModelArtsStopped), string(models.JobSucceeded))
} else if jobStatus != "all" && jobStatus != "" {
jobStatuses = append(jobStatuses, jobStatus)
}
@@ -151,8 +151,9 @@ func DownloadCloudBrains(ctx *context.Context) {
Page: page,
PageSize: pageSize,
},
Type: modelarts.DebugType,
NeedRepoInfo: true,
Type: modelarts.DebugType,
NeedRepoInfo: true,
IsLatestVersion: modelarts.IsLatestVersion,
})
if err != nil {
log.Warn("Can not get cloud brain info", err)
@@ -178,7 +179,7 @@ func DownloadCloudBrains(ctx *context.Context) {
}

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.Status, getCellName("C", row): rs.JobType, getCellName("D", row): time.Unix(int64(rs.Cloudbrain.CreatedUnix), 0).Format(CREATE_TIME_FORMAT), getCellName("E", row): getDurationTime(rs),
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,
}
}
@@ -215,7 +216,7 @@ func getTotalPage(total int64, pageSize int) int {

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

return map[string]string{"A1": ctx.Tr("repo.cloudbrain_task"), "B1": ctx.Tr("repo.modelarts.status"), "C1": ctx.Tr("repo.cloudbrain_task_type"), "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")}
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")}

}



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

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

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

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
@@ -879,9 +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", repo.GetCloudbrainTask)
// m.Get("/:jobname/log", repo.CloudbrainGetLog)
m.Get("/:jobname/log", repo.CloudbrainGetLog)
}, reqRepoReader(models.UnitTypeCloudBrain))
m.Group("/modelarts", func() {
m.Group("/notebook", func() {
@@ -997,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())
}



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

@@ -97,13 +97,10 @@ func GetCloudbrainTask(ctx *context.APIContext) {
}

func CloudbrainGetLog(ctx *context.Context) {
// jobName := ctx.Params(":jobname")
// job, err := models.GetCloudbrainByName(jobName)
jobID := ctx.Params(":jobid")
repoID := ctx.Repo.Repository.ID
job, err := models.GetRepoCloudBrainByJobID(repoID, 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
}
@@ -148,7 +145,7 @@ func CloudbrainGetLog(ctx *context.Context) {
}

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



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

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

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

const tplBindPage base.TplName = "repo/wx_autorize"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 2
- 0
routers/repo/cloudbrain.go View File

@@ -357,6 +357,7 @@ func CloudBrainShow(ctx *context.Context) {
func cloudBrainShow(ctx *context.Context, tpName base.TplName) {
ctx.Data["PageIsCloudBrain"] = true
var jobName = ctx.Params(":jobname")
debugListType := ctx.Query("debugListType")
task, err := models.GetCloudbrainByName(jobName)
if err != nil {
ctx.Data["error"] = err.Error()
@@ -431,6 +432,7 @@ func cloudBrainShow(ctx *context.Context, tpName base.TplName) {
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)
}



+ 8
- 1
routers/repo/modelarts.go View File

@@ -92,6 +92,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)
}

@@ -235,6 +236,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)
@@ -279,6 +281,7 @@ func NotebookShow(ctx *context.Context) {
ctx.Data["jobID"] = jobID
ctx.Data["jobName"] = task.JobName
ctx.Data["result"] = result
ctx.Data["debugListType"] = debugListType
ctx.HTML(200, tplModelArtsNotebookShow)
}

@@ -352,6 +355,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"
@@ -439,7 +446,7 @@ func NotebookDel(ctx *context.Context) {
_, err := modelarts.DelNotebook2(jobID)
if err != nil {
log.Error("DelNotebook2(%s) failed:%v", task.JobName, err.Error())
if strings.Contains(err.Error(), modelarts.NotebookNotFound) {
if strings.Contains(err.Error(), modelarts.NotebookNotFound) || strings.Contains(err.Error(), modelarts.NotebookNoPermission) {
log.Info("old notebook version")
} else {
ctx.ServerError("DelNotebook2 failed", err)


+ 23
- 13
routers/routes/routes.go View File

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

import (
"bytes"
"code.gitea.io/gitea/routers/authentication"
"encoding/gob"
"net/http"
"path"
@@ -274,6 +275,8 @@ func RegisterRoutes(m *macaron.Macaron) {
ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
reqBasicAuth := context.Toggle(&context.ToggleOptions{BasicAuthRequired: true, DisableCSRF: true})
reqWechatBind := context.Toggle(&context.ToggleOptions{WechatAuthRequired: true})
reqWechatBindForApi := context.Toggle(&context.ToggleOptions{WechatAuthRequiredForAPI: true})

bindIgnErr := binding.BindIgnErr
validation.AddBindingRules()
@@ -392,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)
@@ -987,13 +997,13 @@ func RegisterRoutes(m *macaron.Macaron) {
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)
@@ -1005,8 +1015,8 @@ func RegisterRoutes(m *macaron.Macaron) {
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())
@@ -1054,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() {
@@ -1065,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)
})
@@ -1081,8 +1091,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/result_download", cloudbrain.AdminOrJobCreaterRight, repo.ResultDownload)
m.Get("/downloadall", repo.DownloadMultiResultFile)
})
m.Get("/create", reqRepoCloudBrainWriter, repo.InferenceJobNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.InferenceJobNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate)
})
}, context.RepoRef())



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

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

var opTypes = []int{1, 2, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 22, 23, 24, 25, 26, 27, 28, 29, 30}
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


+ 102
- 6
templates/admin/cloudbrain/list.tmpl View File

@@ -60,12 +60,13 @@
</div>
</div>
{{range .Tasks}}
{{if .Repo}}
<div class="ui grid stackable item">
<div class="row">
<!-- 任务名 -->
<div class="two wide column nowrap">
{{if eq .JobType "DEBUG"}}
<a class="title" href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}/cloudbrain/{{.JobName}}" title="{{.JobName}}" style="font-size: 14px;">
{{if or (eq .JobType "DEBUG") (eq .JobType "SNN4IMAGENET") (eq .JobType "BRAINSCORE")}}
<a class="title" href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain/{{.JobName}}{{else}}/modelarts/notebook/{{.JobID}}{{end}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "INFERENCE"}}
@@ -121,7 +122,7 @@
<span class="fitted">{{.JobName}}</span>
</div>
<div class="two wide column text center nowrap" style="width: 17.5%!important;">
{{if eq .JobType "DEBUG"}}
{{if eq .JobType "DEBUG" "SNN4IMAGENET" "BRAINSCORE"}}
<div class="ui compact buttons">
<form id="debugAgainForm-{{.JobID}}">
{{$.CsrfTokenHtml}}
@@ -139,7 +140,7 @@
{{end}}
<!-- 停止任务 -->
<div class="ui compact buttons">
{{if eq .JobType "DEBUG" "BENCHMARK"}}
{{if eq .JobType "DEBUG" "BENCHMARK" "SNN4IMAGENET" "BRAINSCORE"}}
<form id="stopForm-{{.JobID}}" style="margin-left:-1px;">
{{$.CsrfTokenHtml}}
<a style="padding: 0.5rem 1rem;" id="ai-stop-{{.JobID}}" class='ui basic ai_stop {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED" "SUCCEEDED" "STOPPED" "STOPPING"}}disabled {{else}} blue {{end}}button' data-repopath='{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else if eq .JobType "BENCHMARK" }}/cloudbrain/benchmark{{else if eq .ComputeResource "NPU" }}/modelarts/notebook{{end}}/{{.JobID}}/stop' data-jobid="{{.JobID}}">
@@ -162,6 +163,102 @@
</div>
</div>
</div>
{{else}}
<div class="ui grid stackable item">
<div class="row">
<!-- 任务名 -->
<div class="two wide column nowrap">
{{if eq .JobType "DEBUG"}}
<a class="title" href="" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "INFERENCE"}}
<a class="title" href="" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "TRAIN"}}
<a class="title" href="" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "BENCHMARK"}}
<a class="title" href="" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{end}}
</div>
<!-- 任务类型 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;">{{.JobType}} </span>
</div>
<!-- 任务状态 -->
<div class="two wide column text center nowrap" style="padding-left: 2.2rem !important; width: 10% !important;">
<span class="job-status" id="{{.JobID}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
<span><i id="{{.JobID}}-icon" style="vertical-align: middle;" class="{{.Status}}"></i><span id="{{.JobID}}-text" style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span>
</span>
</div>
<!-- 任务创建时间 -->
<div class="two wide column text center nowrap" style="width: 10% !important;">
<span style="font-size: 12px;" class="">{{TimeSinceUnix1 .Cloudbrain.CreatedUnix}}</span>
</div>
<!-- 任务运行时间 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;" id="duration-{{.JobID}}">{{if .TrainJobDuration}}{{.TrainJobDuration}}{{else}}--{{end}}</span>
</div>
<!-- 计算资源 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;">{{if .ComputeResource}}{{.ComputeResource}}{{else}}--{{end}}</span>
</div>
<!-- 创建者 -->
<div class="one wide column text center nowrap">
{{if .User.Name}}
<a href="{{AppSubUrl}}/{{.User.Name}}" title="{{.User.Name}}"><img class="ui avatar image" src="{{.User.RelAvatarLink}}"></a>
{{else}}
<a title="Ghost"><img class="ui avatar image" src="{{AppSubUrl}}/user/avatar/Ghost/-1"></a>
{{end}}
</div>
<!-- 项目 -->
<div class="two wide column text center nowrap">
<a href="" title="">--</a>
</div>
<!-- 云脑侧名称 -->
<div class="two wide column text center nowrap" style="overflow: hidden;text-overflow:ellipsis;">
<span class="fitted">{{.JobName}}</span>
</div>
<div class="two wide column text center nowrap" style="width: 17.5%!important;">
{{if eq .JobType "DEBUG"}}
<div class="ui compact buttons">
<form id="debugAgainForm-{{.JobID}}">
{{$.CsrfTokenHtml}}
{{if eq .Status "RUNNING" "WAITING" "CREATING" "STARTING"}}
<a style="margin: 0 1rem;" id="ai-debug-{{.JobID}}" class='ui basic disabled button' >
{{$.i18n.Tr "repo.debug"}}
</a>
{{else}}
<a id="ai-debug-{{.JobID}}" class='ui basic disabled button' >
{{$.i18n.Tr "repo.debug_again"}}
</a>
{{end}}
</form>
</div>
{{end}}
<!-- 停止任务 -->
<div class="ui compact buttons">
<a style="padding: 0.5rem 1rem;" id="ai-stop-{{.JobID}}" class="ui basic disabled button" data-jobid="{{.JobID}}" data-version="{{.VersionName}}" >
{{$.i18n.Tr "repo.stop"}}
</a>
</div>
<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action='' method="post">
{{$.CsrfTokenHtml}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="ai-delete-{{.JobID}}" class="ui basic disabled button" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
</form>
</div>
</div>
</div>

{{end}}
{{end}}
<div id="app" style="margin-top: 2rem;">
<div class="center">
@@ -209,12 +306,11 @@ function getParams(){
const params = new URLSearchParams(window.location.search)
let jobType = !params.get('jobType')? '{{.i18n.Tr "admin.cloudbrain.all_task_types"}}' : params.get('jobType')
let listType = !params.get('listType')? '{{.i18n.Tr "admin.cloudbrain.all_computing_resources"}}' : params.get('listType')
let jobStatus = !params.get('jobStatus')? '{{.i18n.Tr "admin.cloudbrain.all_status"}}' : params.get('jobStatus')
let jobStatus = !params.get('jobStatus')? '{{.i18n.Tr "admin.cloudbrain.all_status"}}' : params.get('jobStatus').toUpperCase()
const dropdownValueArray = [jobType,listType,jobStatus]
$('#adminCloud .default.text ').each(function(index,e){
$(e).text(dropdownValueArray[index])
})
}
getParams()
console.log({{.Tasks}})
</script>

+ 14
- 10
templates/admin/cloudbrain/search.tmpl View File

@@ -1,7 +1,7 @@
<div class="ui attached segment">
<form class="ui form ignore-dirty" style="max-width: 90%">
<div class="ui fluid action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "admin.cloudbrain.search"}}..." autofocus>
<button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
</div>
</form>
@@ -16,6 +16,8 @@
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=TRAIN&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="TRAIN">TRAIN</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=INFERENCE&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="INFERENCE">INFERENCE</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=BENCHMARK&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="BENCHMARK">BENCHMARK</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=SNN4IMAGENET&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="BENCHMARK">SNN4IMAGENET</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=BRAINSCORE&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="BENCHMARK">BRAINSCORE</a>
</div>
</div>
<div class="ui selection dropdown" style="min-width: 10em;min-height:2.6em;border-radius: .28571429rem;margin-right: 1em;padding: .67em 3.2em .7em 1em;">
@@ -32,15 +34,17 @@
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=" data-value='{{.i18n.Tr "admin.cloudbrain.all_status"}}'>{{.i18n.Tr "admin.cloudbrain.all_status"}}</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STARTING" data-value="CPU/GPU">STARTING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=RESTARTING" data-value="NPU">RESTARTING </a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=START_FAILED" data-value="all">START_FAILED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STOPPING" data-value="CPU/GPU">STOPPING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STOPPED" data-value="NPU">STOPPED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=WAITING" data-value="all">WAITING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=COMPLETED" data-value="CPU/GPU">COMPLETED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=FAILED" data-value="NPU">FAILED </a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=OTHER" data-value="NPU">OTHER</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STARTING" data-value="STARTING">STARTING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=RUNNING" data-value="RUNNING">RUNNING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=RESTARTING" data-value="RESTARTING">RESTARTING </a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=START_FAILED" data-value="START_FAILED">START_FAILED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STOPPING" data-value="STOPPING">STOPPING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STOPPED" data-value="STOPPED">STOPPED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=WAITING" data-value="WAITING">WAITING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=COMPLETED" data-value="COMPLETED">COMPLETED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=SUCCEEDED" data-value="SUCCEEDED">SUCCEEDED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=FAILED" data-value="FAILED">FAILED </a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=other" data-value="OTHER">OTHER</a>
</div>
</div>

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

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

</style>
<!-- <div class="ui page dimmer">
@@ -113,7 +116,7 @@
<div class="eight wide field" id="engine_name">
<input type="hidden" id="benchmark_child_types_id_hidden" name="benchmark_child_types_id_hidden" value="{{.benchmark_child_types_id_hidden}}">
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.cloudbrain.benchmark.evaluate_child_type"}}</label>
<select class="ui fluid selection dropdown nowrap" id="benchmark_child_types_id" style='width: 100%;' name="benchmark_child_types_id">
<select class="ui fluid selection dropdown nowrapx" id="benchmark_child_types_id" style='width: 100%;' name="benchmark_child_types_id">
</select>
</div>
</div>


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

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


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

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


+ 4
- 2
templates/repo/debugjob/index.tmpl View File

@@ -328,7 +328,7 @@
{{$.i18n.Tr "repo.debug"}}
</a>
{{else}}
<a id="ai-debug-{{.JobID}}" class='ui basic ai_debug {{if eq .Status "CREATING" "STOPPING" "WAITING" "STARTING"}} disabled {{else}}blue {{end}}button' data-jobid="{{.JobID}}" data-repopath='{{$.RepoLink}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}/{{.JobID}}/'>
<a id="ai-debug-{{.JobID}}" class='ui basic ai_debug {{if eq .Status "CREATING" "STOPPING" "WAITING" "STARTING"}} disabled {{else}}blue {{end}}button' data-jobid="{{.JobID}}" data-repopath='{{$.RepoLink}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}/{{.JobID}}/' data-linkpath='{{$.Link}}'>
{{$.i18n.Tr "repo.debug_again"}}
</a>
{{end}}
@@ -481,10 +481,11 @@
// 调试和评分新开窗口
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
let url={{.RepoLink}}
let redirect_to = {{$.Link}}
let getParam=getQueryVariable('debugListType')
let dropdownValue = ['all','',false].includes(getParam)? '全部' : getParam
localStorage.setItem('all',location.href)
// localStorage.setItem('all',location.href)
function getQueryVariable(variable)
{
let query = window.location.search.substring(1);
@@ -504,6 +505,7 @@
})
$('.ui.selection.dropdown').dropdown({
onChange:function(value){
location.href = `${url}/debugjob?debugListType=${value}`
}
})


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

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

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

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

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

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

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

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


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


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

@@ -101,7 +101,7 @@
<button class="ui green button">
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
<a class="ui button cancel" href="{{.RepoLink}}/debugjob?debugListType=all">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
</div>
</form>
@@ -171,11 +171,8 @@
if(option.innerText===inputValue){
hiddenInput.value = option.getAttribute('data-value');
break
break
}
}


})
</script>

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

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


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

@@ -562,16 +562,13 @@ td, th {
$('input[name="JobId"]').val(obj.JobID)
$('input[name="VersionName"]').val(obj.VersionName).addClass('model_disabled')
$('.ui.dimmer').css({"background-color":"rgb(136, 136, 136,0.7)"})
createModelName()
createModelName()
},
onHide:function(){
document.getElementById("formId").reset();
$('.ui.dimmer').css({"background-color":""})
$('.ui.error.message').text()
$('.ui.error.message').css('display','none')
}
})
.modal('show')


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

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


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

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

+ 1
- 1
web_src/js/components/MinioUploader.vue View File

@@ -81,7 +81,7 @@ export default {
dictInvalidFileType: this.dropzoneParams.data('invalid-input-type'),
dictFileTooBig: this.dropzoneParams.data('file-too-big'),
dictRemoveFile: this.dropzoneParams.data('remove-file'),
// previewTemplate
previewTemplate
});
dropzoneUploader.on('addedfile', (file) => {
setTimeout(() => {


+ 1
- 1
web_src/js/components/ObsUploader.vue View File

@@ -82,7 +82,7 @@ export default {
dictInvalidFileType: this.dropzoneParams.data('invalid-input-type'),
dictFileTooBig: this.dropzoneParams.data('file-too-big'),
dictRemoveFile: this.dropzoneParams.data('remove-file'),
// previewTemplate
previewTemplate
});
dropzoneUploader.on('addedfile', (file) => {
setTimeout(() => {


+ 150
- 0
web_src/js/components/WxAutorize.vue View File

@@ -0,0 +1,150 @@
<template>
<div>
<div class="ui container">
<div class="ui placeholder segment bgtask-none">
<div class="bgtask-content-header">
<h2 class="wx-title">微信扫码认证</h2>
<p class="wx-desc-top">请绑定微信,然后再使用启智算力环境</p>
</div>
<div class="wx-login">
<div class="qrcode" >
<img class="wx-qrcode" :src="wx_qrcodeImg" v-show="!!wx_qrcodeImg">
<span class="el-icon-loading" v-show="wxLoading"></span>
<img class="wx-qrcode-reload" v-if="qrCodeReload" @click="getWxQrcode(true)" src="https://files.wondercv.com/auth/qrcode_reload.png">
</div>
<div class="wx-desc-bottom" style="color:#919191">微信扫码关注公众号即可完成绑定</div>
</div>
<div class="user-agreement">
<i class="ri-information-line orange"></i>绑定微信代表已阅读并接受<a href="/home/term/"> OpenI启智社区AI协作平台使用协议</a>
</div>
</div>
</div>
</div>
</template>

<script>
const {_AppSubUrl, _StaticUrlPrefix, csrf} = window.config;
export default {
components: {
},
data() {
return {
wx_qrcodeImg:'',
wxLoading:false,
isLogin:false,
SceneStr:'',
status:'',
qrCodeReload:false
};
},
methods: {
getWxQrcode(reloadFlag) {
if(reloadFlag){
this.qrCodeReload=false
}
this.wxLoading = true
this.$axios.get('/authentication/wechat/qrCode4Bind').then((res)=>{
let ticket = res.data.data.Ticket
this.SceneStr = res.data.data.SceneStr
this.wx_qrcodeImg = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='+ticket
this.wxLoading = false
this.WxqrCheck()
})
},
WxqrCheck(){
this.$axios.get(`/authentication/wechat/bindStatus?sceneStr=${this.SceneStr}`).then((res)=>{
this.status = res.data.data.status
this.isLogin = true
})
}
},
watch:{
isLogin: function () {
let times = setInterval(async () => {
if (this.status===0) {
this.WxqrCheck()
} else if (this.status === 9) {
this.qrCodeReload=true
} else if (this.status === 2) {
//用户登录成功后清除定时器
clearInterval(times)
$('.alert').html('绑定成功!').removeClass('alert-danger').addClass('alert-success').show().delay(1500).fadeOut();
window.location.href = this.$Cookies.get('redirect_to')
}
}, 1000)
},
},
mounted() {
this.getWxQrcode(false)
// let QrcodeData = $("#WxAutorize-qrcode").val()
// this.wx_qrcodeImg = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='+QrcodeData
},
created() {
}

};
</script>

<style scoped>
.el-icon-loading{
position: absolute;
font-size: 32px;
color: #bcbcbc;
top: calc(50% - 16px);
left: calc(50% - 16px);
animation: rotating 2s linear infinite;
}
.wx-title{
font-family: SourceHanSansSC-medium;
font-size: 24px;
color: rgba(16, 16, 16, 100);
}
.wx-desc-top{
font-size: 14px;
color: rgba(16, 16, 16, 100);
font-family: SourceHanSansSC-light;
}
.qrcode{
width: 200px;
height: 200px;
line-height: 180px;
text-align: center;
position: relative;
}
.wx-login{
display: flex;
flex-direction: column;
align-items: center;
margin-top: 24px;
}
.wx-qrcode{
width: 100%;
height: 100%;
}
.wx-qrcode-reload{
width: 100%;
height: 100%;
left: 0px;
top: 0px;
position: absolute;
cursor: pointer;
}
.wx-desc-bottom{
color: rgba(145, 145, 145, 100);
font-size: 14px;
font-family: SourceHanSansSC-regular;
}
.user-agreement{
display: flex;
align-items: center;
justify-content: center;
margin-top: 51px;
color: rgba(16, 16, 16, 100);
font-size: 14px;
font-family: SourceHanSansSC-regular;
}
.orange{
color: #f2711c;
}

</style>

+ 8
- 5
web_src/js/features/cloudrbanin.js View File

@@ -195,16 +195,19 @@ export default async function initCloudrain() {
const jobName = this.dataset.jobname
getModelInfo(repoPath,modelName,versionName,jobName)
})
function debugAgain(JobID,debugUrl){
function debugAgain(JobID,debugUrl,redirect_to){
if($('#' + JobID+ '-text').text()==="RUNNING"){
window.open(debugUrl+'debug')
}else{
$.ajax({
type:"POST",
url:debugUrl+'restart',
url:debugUrl+'restart?redirect_to='+redirect_to,
data:$('#debugAgainForm-'+JobID).serialize(),
success:function(res){
if(res.result_code==="0"){
if(res['WechatRedirectUrl']){
window.location.href=res['WechatRedirectUrl']
}
else if(res.result_code==="0"){
if(res.job_id!==JobID){
location.reload()
}else{
@@ -220,7 +223,6 @@ export default async function initCloudrain() {
},
error :function(res){
console.log(res)
}
})
}
@@ -228,7 +230,8 @@ export default async function initCloudrain() {
$('.ui.basic.ai_debug').click(function() {
const jobID = this.dataset.jobid
const repoPath = this.dataset.repopath
debugAgain(jobID,repoPath)
const redirect_to = this.dataset.linkpath
debugAgain(jobID,repoPath,redirect_to)
})
}


+ 17
- 4
web_src/js/index.js View File

@@ -10,6 +10,7 @@ import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from 'axios';
import qs from 'qs';
import Cookies from 'js-cookie'
import 'jquery.are-you-sure';
import './vendor/semanticdropdown.js';
import {svg} from './utils.js';
@@ -40,11 +41,13 @@ import EditTopics from './components/EditTopics.vue';
import DataAnalysis from './components/DataAnalysis.vue'
import Contributors from './components/Contributors.vue'
import Model from './components/Model.vue';
import WxAutorize from './components/WxAutorize.vue'
import initCloudrain from './features/cloudrbanin.js'


Vue.use(ElementUI);
Vue.prototype.$axios = axios;
Vue.prototype.$Cookies = Cookies;
Vue.prototype.qs = qs;
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;

@@ -2921,6 +2924,7 @@ $(document).ready(async () => {
initVueImages();
initVueModel();
initVueDataAnalysis();
initVueWxAutorize();
initTeamSettings();
initCtrlEnterSubmit();
initNavbarContentToggle();
@@ -3618,7 +3622,7 @@ function initVueApp() {
if (!window.location.search) {
window.location.href = this.localHref + '?page='+val
} else if (searchParams.has('page')) {
window.location.href = this.localHref.replace(/page=[0-9]/g,'page='+val)
window.location.href = this.localHref.replace(/page=[0-9]+/g,'page='+val)
} else {
window.location.href=location.href+'&page='+val
}
@@ -3735,7 +3739,16 @@ function initObsUploader() {
template: '<ObsUploader />'
});
}

function initVueWxAutorize() {
const el = document.getElementById('WxAutorize');
if (!el) {
return;
}
new Vue({
el:el,
render: h => h(WxAutorize)
});
}

window.timeAddManual = function () {
$('.mini.modal')
@@ -4166,9 +4179,9 @@ $('.question.circle.icon.cloudbrain-question').hover(function(){
});

//云脑详情页面跳转回上一个页面
$(".section.backTodeBug").attr("href",localStorage.getItem('all'))
// $(".section.backTodeBug").attr("href",localStorage.getItem('all'))
//新建调试取消跳转
$(".ui.button.cancel").attr("href",localStorage.getItem('all'))
// $(".ui.button.cancel").attr("href",localStorage.getItem('all'))

function initcreateRepo(){
let timeout;


Loading…
Cancel
Save