Browse Source

Merge pull request '微信扫码代码' (#1565) from fix-1494 into V20220228

Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/1565
Reviewed-by: lewis <747342561@qq.com>
tags/v1.22.2.2^2
lewis 3 years ago
parent
commit
78047837c2
29 changed files with 1152 additions and 28 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. +87
    -0
      modules/redis/redis_client/client.go
  12. +16
    -0
      modules/redis/redis_key/key_base.go
  13. +14
    -0
      modules/redis/redis_key/wechat_redis_key.go
  14. +40
    -0
      modules/redis/redis_lock/lock.go
  15. +17
    -1
      modules/setting/setting.go
  16. +10
    -0
      package-lock.json
  17. +2
    -0
      package.json
  18. BIN
      public/img/qrcode_reload.png
  19. +7
    -0
      routers/api/v1/api.go
  20. +126
    -0
      routers/authentication/wechat.go
  21. +47
    -0
      routers/authentication/wechat_event.go
  22. +20
    -10
      routers/routes/routes.go
  23. +2
    -1
      templates/repo/debugjob/index.tmpl
  24. +1
    -4
      templates/repo/modelarts/trainjob/show.tmpl
  25. +15
    -0
      templates/repo/wx_autorize.tmpl
  26. +72
    -0
      templates/user/settings/profile.tmpl
  27. +150
    -0
      web_src/js/components/WxAutorize.vue
  28. +8
    -5
      web_src/js/features/cloudrbanin.js
  29. +14
    -1
      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 {


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


+ 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",


BIN
public/img/qrcode_reload.png View File

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

+ 7
- 0
routers/api/v1/api.go View File

@@ -59,6 +59,7 @@
package v1

import (
"code.gitea.io/gitea/routers/authentication"
"net/http"
"strings"

@@ -997,6 +998,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())
}



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

+ 20
- 10
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)
@@ -983,17 +993,17 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", reqRepoCloudBrainReader, repo.CloudBrainShow)
})
m.Group("/:jobid", func() {
m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDebug)
m.Get("/debug", reqWechatBind,cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDebug)
m.Post("/commit_image", cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CommitImageCloudBrainForm{}), repo.CloudBrainCommitImage)
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainDel)
m.Post("/restart", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainRestart)
m.Post("/restart", reqWechatBindForApi, cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainRestart)
m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate)
m.Get("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels)
m.Get("/download_model", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDownloadModel)
})
m.Get("/create", reqRepoCloudBrainWriter, repo.CloudBrainNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate)

m.Group("/benchmark", func() {
m.Get("", reqRepoCloudBrainReader, repo.CloudBrainBenchmarkIndex)
@@ -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())
@@ -1068,8 +1078,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/create_version", cloudbrain.AdminOrJobCreaterRight, repo.TrainJobNewVersion)
m.Post("/create_version", 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())



+ 2
- 1
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,6 +481,7 @@
// 调试和评分新开窗口
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
let url={{.RepoLink}}
let redirect_to = {{$.Link}}
let getParam=getQueryVariable('debugListType')
let dropdownValue = ['all','',false].includes(getParam)? '全部' : getParam


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


+ 72
- 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,77 @@
</div>
</form>
</div>
<h4 class="ui top attached header">
微信绑定<!-- {{.i18n.Tr "settings.avatar"}} -->
</h4>
{{if not .SignedUser.IsBindWechat}}
<div class="ui attached segment">
<a href="/authentication/wechat/bind?redirect_to=/user/settings" class="ui green button">绑定微信</a>
</div>
{{else}}
<div class="ui attached segment">
<table class="ui celled striped table provider titleless">
<thead>
<th class="center aligned">
绑定账号信息
</th>
<!-- <th class="center aligned">
详情
</th> -->
<th class="center aligned">
绑定时间
</th>
<th class="center aligned">
操作
</th>
</thead>
<tbody>
<td class="center aligned">
微信
</td>
<!-- <td class="center aligned">
<img class="ui avatar image" width="24" height="24" src="{{.SignedUser.RelAvatarLink}}">{{.SignedUser.Name}}
</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)">解除绑定</a>
</div>
</td>
</tbody>
</table>
</div>
{{end}}
</div>
<div class="ui mini modal wx-unbind">
<div class="header">确定要解绑微信?</div>
<div class="content">
<p>解绑后将无法使用云脑计算资源</p>
</div>
<div class="actions">
<div class="ui blank cancel button">取消</div>
<div class="ui green approve button">确定</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>

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


+ 14
- 1
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();
@@ -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')


Loading…
Cancel
Save