| @@ -136,6 +136,7 @@ func init() { | |||
| new(AiModelManage), | |||
| new(OfficialTag), | |||
| new(OfficialTagRepos), | |||
| new(WechatBindLog), | |||
| ) | |||
| tablesStatistic = append(tablesStatistic, | |||
| @@ -177,6 +177,10 @@ type User struct { | |||
| //BlockChain | |||
| PublicKey string `xorm:"INDEX"` | |||
| PrivateKey string `xorm:"INDEX"` | |||
| 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", | |||
| @@ -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() | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| package wechat | |||
| type WechatCall interface { | |||
| call() | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 { | |||
| @@ -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", | |||
| }) | |||
| } | |||
| @@ -35,6 +35,7 @@ const ( | |||
| //error code | |||
| modelartsIllegalToken = "ModelArts.6401" | |||
| NotebookNotFound = "ModelArts.6404" | |||
| NotebookNoPermission = "ModelArts.6403" | |||
| ) | |||
| func getRestyClient() *resty.Client { | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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") | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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") | |||
| @@ -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 | |||
| @@ -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钩子 | |||
| @@ -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", | |||
| @@ -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", | |||
| @@ -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; | |||
| } | |||
| @@ -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")} | |||
| } | |||
| @@ -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()) | |||
| } | |||
| @@ -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, | |||
| }) | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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) | |||
| } | |||
| @@ -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) | |||
| @@ -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()) | |||
| @@ -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 | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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" .}} | |||
| @@ -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}` | |||
| } | |||
| }) | |||
| @@ -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}} | |||
| @@ -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> --> | |||
| @@ -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}} | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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') | |||
| @@ -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" .}} | |||
| @@ -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> | |||
| @@ -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(() => { | |||
| @@ -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(() => { | |||
| @@ -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> | |||
| @@ -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) | |||
| }) | |||
| } | |||
| @@ -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; | |||