| @@ -565,6 +565,17 @@ type FlavorInfo struct { | |||
| Desc string `json:"desc"` | |||
| } | |||
| type SpecialPools struct { | |||
| Pools []*SpecialPool `json:"pools"` | |||
| } | |||
| type SpecialPool struct { | |||
| Org string `json:"org"` | |||
| Type string `json:"type"` | |||
| IsExclusive bool `json:"isExclusive"` | |||
| Pool []*GpuInfo `json:"pool"` | |||
| JobType []string `json:"jobType"` | |||
| } | |||
| type ImageInfosModelArts struct { | |||
| ImageInfo []*ImageInfoModelArts `json:"image_info"` | |||
| } | |||
| @@ -15,13 +15,9 @@ type CustomMigrationStatic struct { | |||
| Migrate func(*xorm.Engine, *xorm.Engine) error | |||
| } | |||
| var customMigrations = []CustomMigration{ | |||
| {"Custom v1 Topic struct change to support chinese", syncTopicStruct}, | |||
| } | |||
| var customMigrations []CustomMigration | |||
| var customMigrationsStatic = []CustomMigrationStatic{ | |||
| {"update issue_fixed_rate to 1 if num_issues is 0 ", updateIssueFixedRate}, | |||
| } | |||
| var customMigrationsStatic []CustomMigrationStatic | |||
| func MigrateCustom(x *xorm.Engine) { | |||
| @@ -181,6 +181,7 @@ func SearchDatasetCondition(opts *SearchDatasetOptions) builder.Cond { | |||
| if len(opts.DatasetIDs) > 0 { | |||
| subCon := builder.NewCond() | |||
| subCon = subCon.And(builder.In("dataset.id", opts.DatasetIDs)) | |||
| subCon = generateFilterCond(opts, subCon) | |||
| cond = cond.Or(subCon) | |||
| } | |||
| @@ -460,5 +461,12 @@ func GetCollaboratorDatasetIdsByUserID(userID int64) []int64 { | |||
| _ = x.Table("dataset").Join("INNER", "collaboration", "dataset.repo_id = collaboration.repo_id and collaboration.mode>0 and collaboration.user_id=?", userID). | |||
| Cols("dataset.id").Find(&datasets) | |||
| return datasets | |||
| } | |||
| func GetTeamDatasetIdsByUserID(userID int64) []int64 { | |||
| var datasets []int64 | |||
| _ = x.Table("dataset").Join("INNER", "team_repo", "dataset.repo_id = team_repo.repo_id"). | |||
| Join("INNER", "team_user", "team_repo.team_id=team_user.team_id and team_user.uid=?", userID). | |||
| Cols("dataset.id").Find(&datasets) | |||
| return datasets | |||
| } | |||
| @@ -2749,15 +2749,10 @@ func ReadLatestFileInRepo(userName, repoName, refName, treePath string) (*RepoFi | |||
| log.Error("ReadLatestFileInRepo: Close: %v", err) | |||
| } | |||
| }() | |||
| buf := make([]byte, 1024) | |||
| n, _ := reader.Read(buf) | |||
| if n >= 0 { | |||
| buf = buf[:n] | |||
| } | |||
| d, _ := ioutil.ReadAll(reader) | |||
| commitId := "" | |||
| if blob != nil { | |||
| commitId = fmt.Sprint(blob.ID) | |||
| } | |||
| return &RepoFile{CommitId: commitId, Content: buf}, nil | |||
| return &RepoFile{CommitId: commitId, Content: d}, nil | |||
| } | |||
| @@ -955,6 +955,8 @@ func CounDataByDateAndReCount(wikiCountMap map[string]int, startTime time.Time, | |||
| return err | |||
| } | |||
| userNewAddActivity := make(map[int64]map[int64]int64) | |||
| userAcitvateJsonMap := make(map[int64]map[int64]int64) | |||
| userCurrentDayRegistMap := make(map[int64]map[int64]int64) | |||
| ParaWeight := getParaWeight() | |||
| userMetrics := make(map[string]int) | |||
| var indexTotal int64 | |||
| @@ -1028,7 +1030,10 @@ func CounDataByDateAndReCount(wikiCountMap map[string]int, startTime time.Time, | |||
| log.Info("has activity." + userRecord.Name) | |||
| addUserToMap(userNewAddActivity, userRecord.CreatedUnix, dateRecord.ID) | |||
| } | |||
| if userRecord.IsActive { | |||
| addUserToMap(userAcitvateJsonMap, userRecord.CreatedUnix, dateRecord.ID) | |||
| } | |||
| addUserToMap(userCurrentDayRegistMap, userRecord.CreatedUnix, dateRecord.ID) | |||
| } | |||
| indexTotal += PAGE_SIZE | |||
| @@ -1064,36 +1069,61 @@ func CounDataByDateAndReCount(wikiCountMap map[string]int, startTime time.Time, | |||
| } | |||
| statictisSess.Insert(&useMetrics) | |||
| //update new user activity | |||
| updateNewUserAcitivity(userNewAddActivity, statictisSess) | |||
| updateNewUserAcitivity(userNewAddActivity, userAcitvateJsonMap, userCurrentDayRegistMap, statictisSess) | |||
| return nil | |||
| } | |||
| func updateNewUserAcitivity(currentUserActivity map[int64]map[int64]int64, statictisSess *xorm.Session) { | |||
| for key, value := range currentUserActivity { | |||
| func updateNewUserAcitivity(currentUserActivity map[int64]map[int64]int64, userAcitvateJsonMap map[int64]map[int64]int64, userCurrentDayRegistMap map[int64]map[int64]int64, statictisSess *xorm.Session) { | |||
| for key, value := range userCurrentDayRegistMap { | |||
| useMetrics := &UserMetrics{CountDate: key} | |||
| userAcitvateValue := userAcitvateJsonMap[key] | |||
| HuodongValue := currentUserActivity[key] | |||
| has, err := statictisSess.Get(useMetrics) | |||
| if err == nil && has { | |||
| userIdArrays := strings.Split(useMetrics.HasActivityUserJson, ",") | |||
| for _, userIdStr := range userIdArrays { | |||
| userIdInt, err := strconv.ParseInt(userIdStr, 10, 64) | |||
| if err == nil { | |||
| value[userIdInt] = userIdInt | |||
| } | |||
| } | |||
| userIdArray := "" | |||
| for _, tmpValue := range value { | |||
| userIdArray += fmt.Sprint(tmpValue) + "," | |||
| } | |||
| useMetrics.HasActivityUser = len(value) | |||
| if len(userIdArray) > 0 { | |||
| useMetrics.HasActivityUserJson = userIdArray[0 : len(userIdArray)-1] | |||
| } | |||
| updateSql := "update public.user_metrics set has_activity_user_json='" + useMetrics.HasActivityUserJson + "',regist_activity_user=" + fmt.Sprint(useMetrics.HasActivityUser) + " where count_date=" + fmt.Sprint(key) | |||
| ActivityUserArray, HuodongTotal := setUniqueUserId(useMetrics.HasActivityUserJson, HuodongValue) | |||
| useMetrics.HasActivityUser = HuodongTotal | |||
| useMetrics.HasActivityUserJson = ActivityUserArray | |||
| useMetrics.CurrentDayRegistUser = len(value) | |||
| RegistUserArray, lenRegistUser := setUniqueUserId(useMetrics.ActivityUserJson, userAcitvateValue) | |||
| useMetrics.ActivityUserJson = RegistUserArray | |||
| useMetrics.ActivateRegistUser = lenRegistUser | |||
| updateSql := "update public.user_metrics set has_activity_user_json='" + useMetrics.HasActivityUserJson + | |||
| "',regist_activity_user=" + fmt.Sprint(useMetrics.HasActivityUser) + | |||
| ",activity_user_json='" + useMetrics.ActivityUserJson + "'" + | |||
| ",activate_regist_user=" + fmt.Sprint(useMetrics.ActivateRegistUser) + | |||
| ",not_activate_regist_user=" + fmt.Sprint(useMetrics.CurrentDayRegistUser-useMetrics.ActivateRegistUser) + | |||
| ",current_day_regist_user=" + fmt.Sprint(useMetrics.CurrentDayRegistUser) + | |||
| " where count_date=" + fmt.Sprint(key) | |||
| statictisSess.Exec(updateSql) | |||
| } | |||
| } | |||
| } | |||
| func setUniqueUserId(jsonString string, value map[int64]int64) (string, int) { | |||
| if value == nil { | |||
| value = make(map[int64]int64, 0) | |||
| } | |||
| userIdArrays := strings.Split(jsonString, ",") | |||
| for _, userIdStr := range userIdArrays { | |||
| userIdInt, err := strconv.ParseInt(userIdStr, 10, 64) | |||
| if err == nil { | |||
| value[userIdInt] = userIdInt | |||
| } | |||
| } | |||
| userIdArray := "" | |||
| for _, tmpValue := range value { | |||
| userIdArray += fmt.Sprint(tmpValue) + "," | |||
| } | |||
| if len(userIdArray) > 0 { | |||
| return userIdArray[0 : len(userIdArray)-1], len(value) | |||
| } | |||
| return userIdArray, len(value) | |||
| } | |||
| func addUserToMap(currentUserActivity map[int64]map[int64]int64, registDate timeutil.TimeStamp, userId int64) { | |||
| CountDateTime := time.Date(registDate.Year(), registDate.AsTime().Month(), registDate.AsTime().Day(), 0, 1, 0, 0, registDate.AsTime().Location()) | |||
| CountDate := CountDateTime.Unix() | |||
| @@ -1104,7 +1134,6 @@ func addUserToMap(currentUserActivity map[int64]map[int64]int64, registDate time | |||
| } else { | |||
| currentUserActivity[CountDate][userId] = userId | |||
| } | |||
| } | |||
| func setUserMetrics(userMetrics map[string]int, user *User, start_time int64, end_time int64, dateRecord UserBusinessAnalysis) { | |||
| @@ -467,11 +467,11 @@ type UserAnalysisPara struct { | |||
| type UserMetrics struct { | |||
| CountDate int64 `xorm:"pk"` | |||
| ActivateRegistUser int `xorm:"NOT NULL DEFAULT 0"` | |||
| NotActivateRegistUser int `xorm:"NOT NULL DEFAULT 0"` | |||
| ActivateIndex float64 `xorm:"NOT NULL DEFAULT 0"` | |||
| RegistActivityUser int `xorm:"NOT NULL DEFAULT 0"` | |||
| HasActivityUser int `xorm:"NOT NULL DEFAULT 0"` | |||
| ActivateRegistUser int `xorm:"NOT NULL DEFAULT 0"` //当天激活用户 | |||
| NotActivateRegistUser int `xorm:"NOT NULL DEFAULT 0"` //当天未激活用户 | |||
| ActivateIndex float64 `xorm:"NOT NULL DEFAULT 0"` //激活比率 | |||
| RegistActivityUser int `xorm:"NOT NULL DEFAULT 0"` //当天注册激活的人中,有贡献活动的人 | |||
| HasActivityUser int `xorm:"NOT NULL DEFAULT 0"` //当天有贡献活动的人 | |||
| TotalUser int `xorm:"NOT NULL DEFAULT 0"` | |||
| TotalRegistUser int `xorm:"-"` | |||
| TotalActivateRegistUser int `xorm:"NOT NULL DEFAULT 0"` | |||
| @@ -480,5 +480,7 @@ type UserMetrics struct { | |||
| DisplayDate string `xorm:"-"` | |||
| DataDate string `xorm:"NULL"` | |||
| DaysForMonth int `xorm:"NOT NULL DEFAULT 0"` | |||
| HasActivityUserJson string `xorm:"text NULL"` | |||
| HasActivityUserJson string `xorm:"text NULL"` //贡献活动用户列表 | |||
| ActivityUserJson string `xorm:"text NULL"` //激活用户列表 | |||
| CurrentDayRegistUser int `xorm:"NOT NULL DEFAULT 0"` //当天注册用户 | |||
| } | |||
| @@ -0,0 +1,139 @@ | |||
| package wechat | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "encoding/json" | |||
| "github.com/patrickmn/go-cache" | |||
| "strings" | |||
| "time" | |||
| ) | |||
| var WechatReplyCache = cache.New(2*time.Minute, 1*time.Minute) | |||
| const ( | |||
| WECHAT_REPLY_CACHE_KEY = "wechat_response" | |||
| ) | |||
| const ( | |||
| ReplyTypeText = "text" | |||
| ReplyTypeImage = "image" | |||
| ReplyTypeVoice = "voice" | |||
| ReplyTypeVideo = "video" | |||
| ReplyTypeMusic = "music" | |||
| ReplyTypeNews = "news" | |||
| ) | |||
| type ReplyConfigType string | |||
| const ( | |||
| SubscribeReply ReplyConfigType = "subscribe" | |||
| AutoMsgReply ReplyConfigType = "autoMsg" | |||
| ) | |||
| func (r ReplyConfigType) Name() string { | |||
| switch r { | |||
| case SubscribeReply: | |||
| return "subscribe" | |||
| case AutoMsgReply: | |||
| return "autoMsg" | |||
| } | |||
| return "" | |||
| } | |||
| func (r ReplyConfigType) TreePath() string { | |||
| switch r { | |||
| case SubscribeReply: | |||
| return setting.TreePathOfSubscribe | |||
| case AutoMsgReply: | |||
| return setting.TreePathOfAutoMsgReply | |||
| } | |||
| return "" | |||
| } | |||
| type WechatReplyContent struct { | |||
| Reply *ReplyContent | |||
| ReplyType string | |||
| KeyWords []string | |||
| IsFullMatch int | |||
| } | |||
| type ReplyContent struct { | |||
| Content string | |||
| MediaId string | |||
| Title string | |||
| Description string | |||
| MusicUrl string | |||
| HQMusicUrl string | |||
| ThumbMediaId string | |||
| Articles []ArticlesContent | |||
| } | |||
| func GetAutomaticReply(msg string) *WechatReplyContent { | |||
| r, err := LoadReplyFromCacheAndDisk(AutoMsgReply) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| if r == nil || len(r) == 0 { | |||
| return nil | |||
| } | |||
| for i := 0; i < len(r); i++ { | |||
| if r[i].IsFullMatch == 0 { | |||
| for _, v := range r[i].KeyWords { | |||
| if strings.Contains(msg, v) { | |||
| return r[i] | |||
| } | |||
| } | |||
| } else if r[i].IsFullMatch > 0 { | |||
| for _, v := range r[i].KeyWords { | |||
| if msg == v { | |||
| return r[i] | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func loadReplyFromDisk(replyConfig ReplyConfigType) ([]*WechatReplyContent, error) { | |||
| log.Info("LoadReply from disk") | |||
| repo, err := models.GetRepositoryByOwnerAndAlias(setting.UserNameOfWechatReply, setting.RepoNameOfWechatReply) | |||
| if err != nil { | |||
| log.Error("get AutomaticReply repo failed, error=%v", err) | |||
| return nil, err | |||
| } | |||
| repoFile, err := models.ReadLatestFileInRepo(setting.UserNameOfWechatReply, repo.Name, setting.RefNameOfWechatReply, replyConfig.TreePath()) | |||
| if err != nil { | |||
| log.Error("get AutomaticReply failed, error=%v", err) | |||
| return nil, err | |||
| } | |||
| res := make([]*WechatReplyContent, 0) | |||
| json.Unmarshal(repoFile.Content, &res) | |||
| if res == nil || len(res) == 0 { | |||
| return nil, err | |||
| } | |||
| return res, nil | |||
| } | |||
| func LoadReplyFromCacheAndDisk(replyConfig ReplyConfigType) ([]*WechatReplyContent, error) { | |||
| v, success := WechatReplyCache.Get(replyConfig.Name()) | |||
| if success { | |||
| log.Info("LoadReply from cache,value = %v", v) | |||
| if v == nil { | |||
| return nil, nil | |||
| } | |||
| n := v.([]*WechatReplyContent) | |||
| return n, nil | |||
| } | |||
| content, err := loadReplyFromDisk(replyConfig) | |||
| if err != nil { | |||
| log.Error("LoadReply failed, error=%v", err) | |||
| WechatReplyCache.Set(replyConfig.Name(), nil, 30*time.Second) | |||
| return nil, err | |||
| } | |||
| WechatReplyCache.Set(replyConfig.Name(), content, 60*time.Second) | |||
| return content, nil | |||
| } | |||
| @@ -17,7 +17,8 @@ var ( | |||
| const ( | |||
| GRANT_TYPE = "client_credential" | |||
| ACCESS_TOKEN_PATH = "/cgi-bin/token" | |||
| QR_CODE_Path = "/cgi-bin/qrcode/create" | |||
| QR_CODE_PATH = "/cgi-bin/qrcode/create" | |||
| GET_MATERIAL_PATH = "/cgi-bin/material/batchget_material" | |||
| ACTION_QR_STR_SCENE = "QR_STR_SCENE" | |||
| ERR_CODE_ACCESSTOKEN_EXPIRE = 42001 | |||
| @@ -40,6 +41,11 @@ type QRCodeRequest struct { | |||
| Action_info ActionInfo `json:"action_info"` | |||
| Expire_seconds int `json:"expire_seconds"` | |||
| } | |||
| type MaterialRequest struct { | |||
| Type string `json:"type"` | |||
| Offset int `json:"offset"` | |||
| Count int `json:"count"` | |||
| } | |||
| type ActionInfo struct { | |||
| Scene Scene `json:"scene"` | |||
| @@ -97,7 +103,7 @@ func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) { | |||
| SetQueryParam("access_token", GetWechatAccessToken()). | |||
| SetBody(bodyJson). | |||
| SetResult(&result). | |||
| Post(setting.WechatApiHost + QR_CODE_Path) | |||
| Post(setting.WechatApiHost + QR_CODE_PATH) | |||
| if err != nil { | |||
| log.Error("create QR code failed,e=%v", err) | |||
| return nil, false | |||
| @@ -113,6 +119,37 @@ func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) { | |||
| return &result, false | |||
| } | |||
| //getMaterial | |||
| // api doc: https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Get_materials_list.html | |||
| func getMaterial(mType string, offset, count int) (interface{}, bool) { | |||
| client := getWechatRestyClient() | |||
| body := &MaterialRequest{ | |||
| Type: mType, | |||
| Offset: offset, | |||
| Count: count, | |||
| } | |||
| bodyJson, _ := json.Marshal(body) | |||
| r, err := client.R(). | |||
| SetHeader("Content-Type", "application/json"). | |||
| SetQueryParam("access_token", GetWechatAccessToken()). | |||
| SetBody(bodyJson). | |||
| Post(setting.WechatApiHost + GET_MATERIAL_PATH) | |||
| if err != nil { | |||
| log.Error("create QR code failed,e=%v", err) | |||
| return nil, false | |||
| } | |||
| a := r.Body() | |||
| resultMap := make(map[string]interface{}, 0) | |||
| json.Unmarshal(a, &resultMap) | |||
| errcode := resultMap["errcode"] | |||
| if errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_EXPIRE) || errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_INVALID) { | |||
| return nil, true | |||
| } | |||
| log.Info("%v", r) | |||
| return &resultMap, false | |||
| } | |||
| func getErrorCodeFromResponse(r *resty.Response) int { | |||
| a := r.Body() | |||
| resultMap := make(map[string]interface{}, 0) | |||
| @@ -18,7 +18,7 @@ import ( | |||
| // <EventKey><![CDATA[SCENE_VALUE]]></EventKey> | |||
| // <Ticket><![CDATA[TICKET]]></Ticket> | |||
| //</xml> | |||
| type WechatEvent struct { | |||
| type WechatMsg struct { | |||
| ToUserName string | |||
| FromUserName string | |||
| CreateTime int64 | |||
| @@ -26,9 +26,13 @@ type WechatEvent struct { | |||
| Event string | |||
| EventKey string | |||
| Ticket string | |||
| Content string | |||
| MsgId string | |||
| MsgDataId string | |||
| Idx string | |||
| } | |||
| type EventReply struct { | |||
| type MsgReply struct { | |||
| XMLName xml.Name `xml:"xml"` | |||
| ToUserName string | |||
| FromUserName string | |||
| @@ -37,16 +41,97 @@ type EventReply struct { | |||
| Content string | |||
| } | |||
| type TextMsgReply struct { | |||
| XMLName xml.Name `xml:"xml"` | |||
| ToUserName string | |||
| FromUserName string | |||
| CreateTime int64 | |||
| MsgType string | |||
| Content string | |||
| } | |||
| type ImageMsgReply struct { | |||
| XMLName xml.Name `xml:"xml"` | |||
| ToUserName string | |||
| FromUserName string | |||
| CreateTime int64 | |||
| MsgType string | |||
| Image ImageContent | |||
| } | |||
| type VoiceMsgReply struct { | |||
| XMLName xml.Name `xml:"xml"` | |||
| ToUserName string | |||
| FromUserName string | |||
| CreateTime int64 | |||
| MsgType string | |||
| Voice VoiceContent | |||
| } | |||
| type VideoMsgReply struct { | |||
| XMLName xml.Name `xml:"xml"` | |||
| ToUserName string | |||
| FromUserName string | |||
| CreateTime int64 | |||
| MsgType string | |||
| Video VideoContent | |||
| } | |||
| type MusicMsgReply struct { | |||
| XMLName xml.Name `xml:"xml"` | |||
| ToUserName string | |||
| FromUserName string | |||
| CreateTime int64 | |||
| MsgType string | |||
| Music MusicContent | |||
| } | |||
| type NewsMsgReply struct { | |||
| XMLName xml.Name `xml:"xml"` | |||
| ToUserName string | |||
| FromUserName string | |||
| CreateTime int64 | |||
| MsgType string | |||
| ArticleCount int | |||
| Articles ArticleItem | |||
| } | |||
| type ArticleItem struct { | |||
| Item []ArticlesContent | |||
| } | |||
| type ImageContent struct { | |||
| MediaId string | |||
| } | |||
| type VoiceContent struct { | |||
| MediaId string | |||
| } | |||
| type VideoContent struct { | |||
| MediaId string | |||
| Title string | |||
| Description string | |||
| } | |||
| type MusicContent struct { | |||
| Title string | |||
| Description string | |||
| MusicUrl string | |||
| HQMusicUrl string | |||
| ThumbMediaId string | |||
| } | |||
| type ArticlesContent struct { | |||
| XMLName xml.Name `xml:"item"` | |||
| Title string | |||
| Description string | |||
| PicUrl string | |||
| Url string | |||
| } | |||
| const ( | |||
| WECHAT_EVENT_SUBSCRIBE = "subscribe" | |||
| WECHAT_EVENT_SCAN = "SCAN" | |||
| ) | |||
| const ( | |||
| WECHAT_MSG_TYPE_TEXT = "text" | |||
| WECHAT_MSG_TYPE_TEXT = "text" | |||
| WECHAT_MSG_TYPE_EVENT = "event" | |||
| ) | |||
| func HandleSubscribeEvent(we WechatEvent) string { | |||
| func HandleScanEvent(we WechatMsg) string { | |||
| eventKey := we.EventKey | |||
| if eventKey == "" { | |||
| return "" | |||
| @@ -74,3 +159,11 @@ func HandleSubscribeEvent(we WechatEvent) string { | |||
| return BIND_REPLY_SUCCESS | |||
| } | |||
| func HandleSubscribeEvent(we WechatMsg) *WechatReplyContent { | |||
| r, err := LoadReplyFromCacheAndDisk(SubscribeReply) | |||
| if err != nil || len(r) == 0 { | |||
| return nil | |||
| } | |||
| return r[0] | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| package wechat | |||
| import "code.gitea.io/gitea/modules/log" | |||
| func GetWechatMaterial(mType string, offset, count int) interface{} { | |||
| result, retryFlag := getMaterial(mType, offset, count) | |||
| if retryFlag { | |||
| log.Info("retryGetWechatMaterial calling") | |||
| refreshAccessToken() | |||
| result, _ = getMaterial(mType, offset, count) | |||
| } | |||
| return result | |||
| } | |||
| @@ -1,12 +1,16 @@ | |||
| package grampus | |||
| import ( | |||
| "encoding/json" | |||
| "strings" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/notification" | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "strings" | |||
| ) | |||
| const ( | |||
| @@ -15,12 +19,14 @@ const ( | |||
| ProcessorTypeNPU = "npu.huawei.com/NPU" | |||
| ProcessorTypeGPU = "nvidia.com/gpu" | |||
| CommandPrepareScript = "pwd;cd /cache;mkdir -p output;mkdir -p code;mkdir -p dataset;echo \"start loading script\";wget -q https://git.openi.org.cn/OpenIOSSG/script_for_grampus/archive/master.zip;" + | |||
| GpuWorkDir = "/tmp/" | |||
| NpuWorkDir = "/cache/" | |||
| CommandPrepareScript = ";mkdir -p output;mkdir -p code;mkdir -p dataset;echo \"start loading script\";wget -q https://git.openi.org.cn/OpenIOSSG/script_for_grampus/archive/master.zip;" + | |||
| "echo \"finish loading script\";unzip -q master.zip;cd script_for_grampus;chmod 777 downloader_for_obs uploader_for_obs downloader_for_minio uploader_for_minio;" | |||
| //CommandPrepareScript = "pwd;cd /cache;mkdir -p output;mkdir -p code;mkdir -p dataset;echo \"start loading script\";wget -q https://git.openi.org.cn/OpenIOSSG/script_for_grampus/archive/master.zip;" + | |||
| // "echo \"finish loading script\";unzip -q master.zip;cd script_for_grampus;chmod 777 downloader_for_obs uploader_for_obs downloader_for_minio uploader_for_minio;" | |||
| //CommandPrepareScript = "bash;pwd;apt-get -y update;apt-get -y upgrade;apt-get -y install wget;apt-get -y install unzip;" + | |||
| // "cd /tmp;mkdir -p output;mkdir -p code;mkdir -p dataset;wget -q https://git.openi.org.cn/OpenIOSSG/script_for_grampus/archive/master.zip;" + | |||
| // "unzip -q master.zip;cd script_for_grampus;chmod 777 downloader_for_obs uploader_for_obs downloader_for_minio uploader_for_minio;" | |||
| CodeArchiveName = "master.zip" | |||
| ) | |||
| @@ -28,6 +34,8 @@ var ( | |||
| poolInfos *models.PoolInfos | |||
| FlavorInfos *models.FlavorInfos | |||
| ImageInfos *models.ImageInfosModelArts | |||
| SpecialPools *models.SpecialPools | |||
| ) | |||
| type GenerateTrainJobReq struct { | |||
| @@ -63,6 +71,27 @@ type GenerateTrainJobReq struct { | |||
| func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (err error) { | |||
| createTime := timeutil.TimeStampNow() | |||
| var CenterID []string | |||
| var CenterName []string | |||
| if SpecialPools != nil { | |||
| for _, pool := range SpecialPools.Pools { | |||
| if !pool.IsExclusive && strings.Contains(req.ComputeResource, pool.Type) { | |||
| org, _ := models.GetOrgByName(pool.Org) | |||
| if org != nil { | |||
| isOrgMember, _ := models.IsOrganizationMember(org.ID, ctx.User.ID) | |||
| if isOrgMember { | |||
| for _, info := range pool.Pool { | |||
| CenterID = append(CenterID, info.Queue) | |||
| CenterName = append(CenterName, info.Value) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| jobResult, err := createJob(models.CreateGrampusJobRequest{ | |||
| Name: req.JobName, | |||
| Tasks: []models.GrampusTasks{ | |||
| @@ -72,6 +101,8 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (err error | |||
| ResourceSpecId: req.ResourceSpecId, | |||
| ImageId: req.ImageId, | |||
| ImageUrl: req.ImageUrl, | |||
| CenterID: CenterID, | |||
| CenterName: CenterName, | |||
| ReplicaNum: 1, | |||
| }, | |||
| }, | |||
| @@ -136,3 +167,8 @@ func TransTrainJobStatus(status string) string { | |||
| return strings.ToUpper(status) | |||
| } | |||
| func InitSpecialPool() { | |||
| if SpecialPools == nil && setting.Grampus.SpecialPools != "" { | |||
| json.Unmarshal([]byte(setting.Grampus.SpecialPools), &SpecialPools) | |||
| } | |||
| } | |||
| @@ -530,10 +530,11 @@ var ( | |||
| //grampus config | |||
| Grampus = struct { | |||
| Env string | |||
| Host string | |||
| UserName string | |||
| Password string | |||
| Env string | |||
| Host string | |||
| UserName string | |||
| Password string | |||
| SpecialPools string | |||
| }{} | |||
| //elk config | |||
| @@ -553,6 +554,13 @@ var ( | |||
| WechatQRCodeExpireSeconds int | |||
| WechatAuthSwitch bool | |||
| //wechat auto reply config | |||
| UserNameOfWechatReply string | |||
| RepoNameOfWechatReply string | |||
| RefNameOfWechatReply string | |||
| TreePathOfAutoMsgReply string | |||
| TreePathOfSubscribe string | |||
| //nginx proxy | |||
| PROXYURL string | |||
| RadarMap = struct { | |||
| @@ -1380,6 +1388,11 @@ func NewContext() { | |||
| WechatAppSecret = sec.Key("APP_SECRET").MustString("e48e13f315adc32749ddc7057585f198") | |||
| WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120) | |||
| WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(true) | |||
| UserNameOfWechatReply = sec.Key("AUTO_REPLY_USER_NAME").MustString("OpenIOSSG") | |||
| RepoNameOfWechatReply = sec.Key("AUTO_REPLY_REPO_NAME").MustString("promote") | |||
| RefNameOfWechatReply = sec.Key("AUTO_REPLY_REF_NAME").MustString("master") | |||
| TreePathOfAutoMsgReply = sec.Key("AUTO_REPLY_TREE_PATH").MustString("wechat/auto_reply.json") | |||
| TreePathOfSubscribe = sec.Key("SUBSCRIBE_TREE_PATH").MustString("wechat/subscribe_reply.json") | |||
| SetRadarMapConfig() | |||
| @@ -1400,6 +1413,8 @@ func GetGrampusConfig() { | |||
| Grampus.Host = sec.Key("SERVER_HOST").MustString("") | |||
| Grampus.UserName = sec.Key("USERNAME").MustString("") | |||
| Grampus.Password = sec.Key("PASSWORD").MustString("") | |||
| Grampus.SpecialPools = sec.Key("SPECIAL_POOL").MustString("") | |||
| } | |||
| func SetRadarMapConfig() { | |||
| @@ -6,6 +6,7 @@ package setting | |||
| import ( | |||
| "net/url" | |||
| "strings" | |||
| "code.gitea.io/gitea/modules/log" | |||
| ) | |||
| @@ -13,14 +14,18 @@ import ( | |||
| var ( | |||
| // Webhook settings | |||
| Webhook = struct { | |||
| QueueLength int | |||
| DeliverTimeout int | |||
| SkipTLSVerify bool | |||
| Types []string | |||
| PagingNum int | |||
| ProxyURL string | |||
| ProxyURLFixed *url.URL | |||
| ProxyHosts []string | |||
| QueueLength int | |||
| DeliverTimeout int | |||
| SkipTLSVerify bool | |||
| Types []string | |||
| PagingNum int | |||
| ProxyURL string | |||
| ProxyURLFixed *url.URL | |||
| ProxyHosts []string | |||
| Socks5Proxy string | |||
| Socks5UserName string | |||
| Socks5Password string | |||
| Socks5ProxyHosts []string | |||
| }{ | |||
| QueueLength: 1000, | |||
| DeliverTimeout: 5, | |||
| @@ -39,6 +44,10 @@ func newWebhookService() { | |||
| Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix"} | |||
| Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10) | |||
| Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("") | |||
| Webhook.Socks5Proxy = sec.Key("SOCKS5_PROXY_URL").MustString("") | |||
| Webhook.Socks5UserName = sec.Key("SOCKS5_USER_NAME").MustString("") | |||
| Webhook.Socks5Password = sec.Key("SOCKS5_PASSWORD").MustString("") | |||
| Webhook.Socks5ProxyHosts = strings.Split(sec.Key("SOCKS5_PROXY_HOST").MustString(""), ";") | |||
| if Webhook.ProxyURL != "" { | |||
| var err error | |||
| Webhook.ProxyURLFixed, err = url.Parse(Webhook.ProxyURL) | |||
| @@ -16,6 +16,8 @@ import ( | |||
| "sync" | |||
| "time" | |||
| "golang.org/x/net/proxy" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/graceful" | |||
| "code.gitea.io/gitea/modules/log" | |||
| @@ -137,8 +139,10 @@ func Deliver(t *models.HookTask) error { | |||
| return | |||
| } | |||
| }() | |||
| match := isSocks5ProxyUrlMatch(req) | |||
| resp, err := makeReq(req, match) | |||
| resp, err := webhookHTTPClient.Do(req) | |||
| if err != nil { | |||
| t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err) | |||
| return err | |||
| @@ -161,6 +165,23 @@ func Deliver(t *models.HookTask) error { | |||
| return nil | |||
| } | |||
| func makeReq(req *http.Request, proxyMatch bool) (*http.Response, error) { | |||
| if proxyMatch { | |||
| return webhookSocks5PoxyHTTPClient.Do(req) | |||
| } | |||
| return webhookHTTPClient.Do(req) | |||
| } | |||
| func isSocks5ProxyUrlMatch(req *http.Request) bool { | |||
| for _, v := range socks5HostMatchers { | |||
| if v.Match(req.URL.Host) { | |||
| return true | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| // DeliverHooks checks and delivers undelivered hooks. | |||
| // FIXME: graceful: This would likely benefit from either a worker pool with dummy queue | |||
| // or a full queue. Then more hooks could be sent at same time. | |||
| @@ -225,9 +246,11 @@ func DeliverHooks(ctx context.Context) { | |||
| } | |||
| var ( | |||
| webhookHTTPClient *http.Client | |||
| once sync.Once | |||
| hostMatchers []glob.Glob | |||
| webhookHTTPClient *http.Client | |||
| once sync.Once | |||
| hostMatchers []glob.Glob | |||
| webhookSocks5PoxyHTTPClient *http.Client | |||
| socks5HostMatchers []glob.Glob | |||
| ) | |||
| func webhookProxy() func(req *http.Request) (*url.URL, error) { | |||
| @@ -274,5 +297,31 @@ func InitDeliverHooks() { | |||
| }, | |||
| } | |||
| if setting.Webhook.Socks5Proxy != "" { | |||
| auth := proxy.Auth{ | |||
| User: setting.Webhook.Socks5UserName, | |||
| Password: setting.Webhook.Socks5Password, | |||
| } | |||
| dialSocksProxy, err := proxy.SOCKS5("tcp", setting.Webhook.Socks5Proxy, &auth, proxy.Direct) | |||
| if err != nil { | |||
| fmt.Println("Error connecting to proxy:", err) | |||
| } | |||
| tr := &http.Transport{Dial: dialSocksProxy.Dial} | |||
| webhookSocks5PoxyHTTPClient = &http.Client{ | |||
| Transport: tr, | |||
| } | |||
| for _, h := range setting.Webhook.Socks5ProxyHosts { | |||
| if g, err := glob.Compile(h); err == nil { | |||
| socks5HostMatchers = append(socks5HostMatchers, g) | |||
| } else { | |||
| log.Error("glob.Compile %s failed: %v", h, err) | |||
| } | |||
| } | |||
| } | |||
| go graceful.GetManager().RunWithShutdownContext(DeliverHooks) | |||
| } | |||
| @@ -1178,6 +1178,8 @@ model.manage.model_accuracy = Model Accuracy | |||
| grampus.train_job.ai_center = AI Center | |||
| grampus.dataset_path_rule = The code is storaged in /cache/code;the dataset is storaged in /cache/dataset;and please put your model into /cache/output, then you can download it online。 | |||
| grampus.gpu_dataset_path_rule = The code is storaged in /tmp/code;the dataset is storaged in /tmp/dataset;and please put your model into /tmp/output, then you can download it online。 | |||
| grampus.no_operate_right = You have no right to do this operation. | |||
| template.items = Template Items | |||
| template.git_content = Git Content (Default Branch) | |||
| @@ -1192,6 +1192,9 @@ model.manage.model_accuracy = 模型精度 | |||
| grampus.train_job.ai_center=智算中心 | |||
| grampus.dataset_path_rule = 训练脚本存储在/cache/code中,数据集存储在/cache/dataset中,训练输出请存储在/cache/output中以供后续下载。 | |||
| grampus.gpu_dataset_path_rule = 训练脚本存储在/tmp/code中,数据集存储在/tmp/dataset中,训练输出请存储在/tmp/output中以供后续下载。 | |||
| grampus.no_operate_right = 您没有权限创建这类任务。 | |||
| template.items=模板选项 | |||
| template.git_content=Git数据(默认分支) | |||
| @@ -1423,7 +1426,7 @@ issues.label_templates.helper=选择标签模板 | |||
| issues.label_templates.use=使用标签集 | |||
| issues.label_templates.fail_to_load_file=加载标签模板文件 '%s' 时发生错误:%v | |||
| issues.add_label_at=添加了标签 <div class="ui label" style="color: %s\; background-color: %s"> %s </div> %s | |||
| issues.remove_label_at=删除了 <div class="ui label" style="color: %s\; background-color: %s">%s</div> label %s 标签 | |||
| issues.remove_label_at=删除了标签 <div class="ui label" style="color: %s\; background-color: %s">%s </div> %s | |||
| issues.add_milestone_at=` %[2]s 添加了里程碑 <b>%[1]s</b>` | |||
| issues.change_milestone_at=`%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>` | |||
| issues.remove_milestone_at=`%[2]s 删除了里程碑 <b>%[1]s</b>` | |||
| @@ -74,28 +74,30 @@ var swiperOrg = new Swiper(".homeorg-list", { | |||
| }, | |||
| }); | |||
| var output = document.getElementById("newmessage"); | |||
| var url = "ws://" + document.location.host + "/action/notification"; | |||
| if(document.location.host == "git.openi.org.cn" || document.URL.startsWith("https")){ | |||
| url = "wss://" + document.location.host + "/action/notification" | |||
| } | |||
| var socket = new WebSocket(url); | |||
| socket.onopen = function () { | |||
| messageQueue = []; | |||
| console.log("message has connected."); | |||
| }; | |||
| var maxSize = 20; | |||
| var html =document.documentElement; | |||
| var lang = html.attributes["lang"] | |||
| var isZh = true; | |||
| if(lang != null && lang.nodeValue =="en-US" ){ | |||
| isZh=false; | |||
| }else{ | |||
| } | |||
| socket.onmessage = function (e) { | |||
| document.onreadystatechange = function () { | |||
| queryRecommendData(); | |||
| var output = document.getElementById("newmessage"); | |||
| var url = "ws://" + document.location.host + "/action/notification"; | |||
| if(document.location.host == "git.openi.org.cn" || document.URL.startsWith("https")){ | |||
| url = "wss://" + document.location.host + "/action/notification" | |||
| } | |||
| var socket = new WebSocket(url); | |||
| socket.onopen = function () { | |||
| messageQueue = []; | |||
| console.log("message has connected."); | |||
| }; | |||
| socket.onmessage = function (e) { | |||
| var data =JSON.parse(e.data) | |||
| var html = ""; | |||
| if (data != null){ | |||
| @@ -154,7 +156,7 @@ socket.onmessage = function (e) { | |||
| html += recordPrefix + actionName; | |||
| html += " <a href=\"" + getRepoLink(record) + "\" rel=\"nofollow\">" + getRepotext(record) + "</a>" | |||
| } | |||
| else if(record.OpType == "24" || record.OpType == "26" || record.OpType == "27" || record.OpType == "28" || record.OpType == "30" || record.OpType == "31"){ | |||
| else if(record.OpType == "24" || record.OpType == "26" || record.OpType == "27" || record.OpType == "28" || record.OpType == "30" || record.OpType == "31" || record.OpType == "32" || record.OpType == "33"){ | |||
| html += recordPrefix + actionName; | |||
| html += " <a href=\"" + getTaskLink(record) + "\" rel=\"nofollow\">" + record.RefName + "</a>" | |||
| } | |||
| @@ -176,7 +178,10 @@ socket.onmessage = function (e) { | |||
| output.innerHTML = html; | |||
| swiperNewMessage.updateSlides(); | |||
| swiperNewMessage.updateProgress(); | |||
| }; | |||
| }; | |||
| } | |||
| function getTaskLink(record){ | |||
| var re = getRepoLink(record); | |||
| @@ -196,6 +201,8 @@ function getTaskLink(record){ | |||
| re = re + "/modelmanage/show_model_info?name=" + record.RefName; | |||
| }else if(record.OpType == 31){ | |||
| re = re + "/cloudbrain/train-job/" + record.Content; | |||
| }else if(record.OpType == 32 || record.OpType == 33){ | |||
| re = re + "/grampus/train-job/" + record.Content; | |||
| } | |||
| re = encodeURI(re); | |||
| return re; | |||
| @@ -369,7 +376,9 @@ var actionNameZH={ | |||
| "28":"创建了推理任务", | |||
| "29":"创建了评测任务", | |||
| "30":"导入了新模型", | |||
| "31":"创建了CPU/GPU类型训练任务" | |||
| "31":"创建了CPU/GPU类型训练任务", | |||
| "32":"创建了NPU类型训练任务", | |||
| "33":"创建了CPU/GPU类型训练任务" | |||
| }; | |||
| var actionNameEN={ | |||
| @@ -396,6 +405,8 @@ var actionNameEN={ | |||
| "29":" created profiling task", | |||
| "30":" created new model", | |||
| "31":" created CPU/GPU type training task", | |||
| "32":" created NPU type training task", | |||
| "33":" created CPU/GPU type training task" | |||
| }; | |||
| var repoAndOrgZH={ | |||
| @@ -437,7 +448,9 @@ function getAction(opType,isZh){ | |||
| } | |||
| } | |||
| queryRecommendData(); | |||
| function queryRecommendData(){ | |||
| $.ajax({ | |||
| @@ -43,12 +43,6 @@ func CloudBrains(ctx *context.Context) { | |||
| if page <= 0 { | |||
| page = 1 | |||
| } | |||
| debugType := models.TypeCloudBrainAll | |||
| if listType == models.GPUResource { | |||
| debugType = models.TypeCloudBrainOne | |||
| } else if listType == models.NPUResource { | |||
| debugType = models.TypeCloudBrainTwo | |||
| } | |||
| var jobTypes []string | |||
| jobTypeNot := false | |||
| @@ -77,13 +71,14 @@ func CloudBrains(ctx *context.Context) { | |||
| PageSize: setting.UI.IssuePagingNum, | |||
| }, | |||
| Keyword: keyword, | |||
| Type: debugType, | |||
| JobTypeNot: jobTypeNot, | |||
| JobStatusNot: jobStatusNot, | |||
| JobStatus: jobStatuses, | |||
| JobTypes: jobTypes, | |||
| NeedRepoInfo: true, | |||
| IsLatestVersion: modelarts.IsLatestVersion, | |||
| ComputeResource: listType, | |||
| Type: models.TypeCloudBrainAll, | |||
| }) | |||
| if err != nil { | |||
| ctx.ServerError("Get job failed:", err) | |||
| @@ -1055,6 +1055,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/prd/event", authentication.ValidEventSource) | |||
| m.Post("/prd/event", authentication.AcceptWechatEvent) | |||
| }) | |||
| m.Get("/wechat/material", authentication.GetMaterial) | |||
| }, securityHeaders(), context.APIContexter(), sudo()) | |||
| } | |||
| @@ -8,9 +8,11 @@ import ( | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/routers/response" | |||
| "encoding/json" | |||
| "errors" | |||
| gouuid "github.com/satori/go.uuid" | |||
| "strconv" | |||
| "time" | |||
| ) | |||
| @@ -124,3 +126,23 @@ func createQRCode4Bind(userId int64) (*QRCodeResponse, error) { | |||
| } | |||
| return result, nil | |||
| } | |||
| // GetMaterial | |||
| func GetMaterial(ctx *context.Context) { | |||
| mType := ctx.Query("type") | |||
| offsetStr := ctx.Query("offset") | |||
| countStr := ctx.Query("count") | |||
| var offset, count int | |||
| if offsetStr == "" { | |||
| offset = 0 | |||
| } else { | |||
| offset, _ = strconv.Atoi(offsetStr) | |||
| } | |||
| if countStr == "" { | |||
| count = 20 | |||
| } else { | |||
| count, _ = strconv.Atoi(countStr) | |||
| } | |||
| r := wechat.GetWechatMaterial(mType, offset, count) | |||
| ctx.JSON(200, response.SuccessWithData(r)) | |||
| } | |||
| @@ -14,24 +14,48 @@ import ( | |||
| // 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{} | |||
| we := wechat.WechatMsg{} | |||
| xml.Unmarshal(b, &we) | |||
| switch we.MsgType { | |||
| case wechat.WECHAT_MSG_TYPE_EVENT: | |||
| HandleEventMsg(ctx, we) | |||
| case wechat.WECHAT_MSG_TYPE_TEXT: | |||
| HandleTextMsg(ctx, 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 | |||
| } | |||
| // ValidEventSource | |||
| func ValidEventSource(ctx *context.Context) { | |||
| echostr := ctx.Query("echostr") | |||
| ctx.Write([]byte(echostr)) | |||
| return | |||
| } | |||
| func HandleEventMsg(ctx *context.Context, msg wechat.WechatMsg) { | |||
| switch msg.Event { | |||
| case wechat.WECHAT_EVENT_SCAN: | |||
| HandleEventScan(ctx, msg) | |||
| case wechat.WECHAT_EVENT_SUBSCRIBE: | |||
| if msg.EventKey != "" { | |||
| HandleEventScan(ctx, msg) | |||
| } else { | |||
| HandleEventSubscribe(ctx, msg) | |||
| } | |||
| } | |||
| } | |||
| func HandleEventScan(ctx *context.Context, msg wechat.WechatMsg) { | |||
| replyStr := wechat.HandleScanEvent(msg) | |||
| if replyStr == "" { | |||
| log.Info("reply str is empty") | |||
| return | |||
| } | |||
| reply := &wechat.EventReply{ | |||
| ToUserName: we.FromUserName, | |||
| FromUserName: we.ToUserName, | |||
| reply := &wechat.MsgReply{ | |||
| ToUserName: msg.FromUserName, | |||
| FromUserName: msg.ToUserName, | |||
| CreateTime: time.Now().Unix(), | |||
| MsgType: wechat.WECHAT_MSG_TYPE_TEXT, | |||
| Content: replyStr, | |||
| @@ -39,9 +63,99 @@ func AcceptWechatEvent(ctx *context.Context) { | |||
| ctx.XML(200, reply) | |||
| } | |||
| // ValidEventSource | |||
| func ValidEventSource(ctx *context.Context) { | |||
| echostr := ctx.Query("echostr") | |||
| ctx.Write([]byte(echostr)) | |||
| return | |||
| func HandleEventSubscribe(ctx *context.Context, msg wechat.WechatMsg) { | |||
| r := wechat.HandleSubscribeEvent(msg) | |||
| if r == nil { | |||
| return | |||
| } | |||
| reply := buildReplyContent(msg, r) | |||
| ctx.XML(200, reply) | |||
| } | |||
| func HandleTextMsg(ctx *context.Context, msg wechat.WechatMsg) { | |||
| r := wechat.GetAutomaticReply(msg.Content) | |||
| if r == nil { | |||
| log.Info("TextMsg reply is empty") | |||
| return | |||
| } | |||
| reply := buildReplyContent(msg, r) | |||
| ctx.XML(200, reply) | |||
| } | |||
| func buildReplyContent(msg wechat.WechatMsg, r *wechat.WechatReplyContent) interface{} { | |||
| reply := &wechat.MsgReply{ | |||
| ToUserName: msg.FromUserName, | |||
| FromUserName: msg.ToUserName, | |||
| CreateTime: time.Now().Unix(), | |||
| MsgType: r.ReplyType, | |||
| } | |||
| switch r.ReplyType { | |||
| case wechat.ReplyTypeText: | |||
| return &wechat.TextMsgReply{ | |||
| ToUserName: msg.FromUserName, | |||
| FromUserName: msg.ToUserName, | |||
| CreateTime: time.Now().Unix(), | |||
| MsgType: r.ReplyType, | |||
| Content: r.Reply.Content, | |||
| } | |||
| case wechat.ReplyTypeImage: | |||
| return &wechat.ImageMsgReply{ | |||
| ToUserName: msg.FromUserName, | |||
| FromUserName: msg.ToUserName, | |||
| CreateTime: time.Now().Unix(), | |||
| MsgType: r.ReplyType, | |||
| Image: wechat.ImageContent{ | |||
| MediaId: r.Reply.MediaId, | |||
| }, | |||
| } | |||
| case wechat.ReplyTypeVoice: | |||
| return &wechat.VoiceMsgReply{ | |||
| ToUserName: msg.FromUserName, | |||
| FromUserName: msg.ToUserName, | |||
| CreateTime: time.Now().Unix(), | |||
| MsgType: r.ReplyType, | |||
| Voice: wechat.VoiceContent{ | |||
| MediaId: r.Reply.MediaId, | |||
| }, | |||
| } | |||
| case wechat.ReplyTypeVideo: | |||
| return &wechat.VideoMsgReply{ | |||
| ToUserName: msg.FromUserName, | |||
| FromUserName: msg.ToUserName, | |||
| CreateTime: time.Now().Unix(), | |||
| MsgType: r.ReplyType, | |||
| Video: wechat.VideoContent{ | |||
| MediaId: r.Reply.MediaId, | |||
| Title: r.Reply.Title, | |||
| Description: r.Reply.Description, | |||
| }, | |||
| } | |||
| case wechat.ReplyTypeMusic: | |||
| return &wechat.MusicMsgReply{ | |||
| ToUserName: msg.FromUserName, | |||
| FromUserName: msg.ToUserName, | |||
| CreateTime: time.Now().Unix(), | |||
| MsgType: r.ReplyType, | |||
| Music: wechat.MusicContent{ | |||
| Title: r.Reply.Title, | |||
| Description: r.Reply.Description, | |||
| MusicUrl: r.Reply.MusicUrl, | |||
| HQMusicUrl: r.Reply.HQMusicUrl, | |||
| ThumbMediaId: r.Reply.ThumbMediaId, | |||
| }, | |||
| } | |||
| case wechat.ReplyTypeNews: | |||
| return &wechat.NewsMsgReply{ | |||
| ToUserName: msg.FromUserName, | |||
| FromUserName: msg.ToUserName, | |||
| CreateTime: time.Now().Unix(), | |||
| MsgType: r.ReplyType, | |||
| ArticleCount: len(r.Reply.Articles), | |||
| Articles: wechat.ArticleItem{ | |||
| Item: r.Reply.Articles}, | |||
| } | |||
| } | |||
| return reply | |||
| } | |||
| @@ -345,7 +345,9 @@ func ExploreDatasets(ctx *context.Context) { | |||
| var datasetsIds []int64 | |||
| if ownerID > 0 { | |||
| datasetsIds = models.GetCollaboratorDatasetIdsByUserID(ownerID) | |||
| collaboratorDatasetsIds := models.GetCollaboratorDatasetIdsByUserID(ownerID) | |||
| teamDatasetsIds := models.GetTeamDatasetIdsByUserID(ownerID) | |||
| datasetsIds = append(collaboratorDatasetsIds, teamDatasetsIds...) | |||
| } | |||
| opts := &models.SearchDatasetOptions{ | |||
| @@ -172,8 +172,8 @@ func DatasetIndex(ctx *context.Context) { | |||
| for _, attachment := range pageAttachments { | |||
| uploader, _ := models.GetUserByID(attachment.UploaderID) | |||
| attachment.Uploader = uploader | |||
| if !strings.HasSuffix(attachment.Name, ".zip") { | |||
| attachment.DecompressState = -1 //非zip文件 | |||
| if !strings.HasSuffix(attachment.Name, ".zip") && !strings.HasSuffix(attachment.Name, ".tar.gz") { | |||
| attachment.DecompressState = -1 //非压缩文件 | |||
| } | |||
| } | |||
| @@ -616,8 +616,14 @@ func DatasetIsCollaborator(ctx *context.Context, dataset *models.Dataset) bool { | |||
| repo.GetOwner() | |||
| if ctx.User != nil { | |||
| if repo.Owner.IsOrganization() { | |||
| if repo.Owner.IsUserPartOfOrg(ctx.User.ID) { | |||
| for _, t := range repo.Owner.Teams { | |||
| org := repo.Owner | |||
| org.Teams, err = org.GetUserTeams(ctx.User.ID) | |||
| if err != nil { | |||
| log.Error("GetUserTeams error:", err.Error()) | |||
| return false | |||
| } | |||
| if org.IsUserPartOfOrg(ctx.User.ID) { | |||
| for _, t := range org.Teams { | |||
| if t.IsMember(ctx.User.ID) && t.HasRepository(repo.ID) { | |||
| return true | |||
| } | |||
| @@ -71,6 +71,25 @@ func grampusTrainJobNewDataPrepare(ctx *context.Context, processType string) err | |||
| ctx.Data["images"] = images.Infos | |||
| } | |||
| grampus.InitSpecialPool() | |||
| ctx.Data["GPUEnabled"] = true | |||
| ctx.Data["NPUEnabled"] = true | |||
| if grampus.SpecialPools != nil { | |||
| for _, pool := range grampus.SpecialPools.Pools { | |||
| if pool.IsExclusive { | |||
| org, _ := models.GetOrgByName(pool.Org) | |||
| if org != nil { | |||
| isOrgMember, _ := models.IsOrganizationMember(org.ID, ctx.User.ID) | |||
| if !isOrgMember { | |||
| ctx.Data[pool.Type+"Enabled"] = false | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| //get valid resource specs | |||
| specs, err := grampus.GetResourceSpecs(processType) | |||
| if err != nil { | |||
| @@ -128,10 +147,18 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain | |||
| image := strings.TrimSpace(form.Image) | |||
| if !jobNamePattern.MatchString(displayJobName) { | |||
| grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplGrampusTrainJobGPUNew, &form) | |||
| return | |||
| } | |||
| errStr := checkSpecialPool(ctx, "GPU") | |||
| if errStr != "" { | |||
| grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) | |||
| ctx.RenderWithErr(errStr, tplGrampusTrainJobGPUNew, &form) | |||
| return | |||
| } | |||
| //check count limit | |||
| count, err := models.GetGrampusCountByUserID(ctx.User.ID, string(models.JobTypeTrain), models.GPUResource) | |||
| if err != nil { | |||
| @@ -263,6 +290,28 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain | |||
| ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/train-job") | |||
| } | |||
| func checkSpecialPool(ctx *context.Context, resourceType string) string { | |||
| grampus.InitSpecialPool() | |||
| if grampus.SpecialPools != nil { | |||
| for _, pool := range grampus.SpecialPools.Pools { | |||
| if pool.IsExclusive && pool.Type == resourceType { | |||
| org, _ := models.GetOrgByName(pool.Org) | |||
| if org != nil { | |||
| isOrgMember, _ := models.IsOrganizationMember(org.ID, ctx.User.ID) | |||
| if !isOrgMember { | |||
| return ctx.Tr("repo.grampus.no_operate_right") | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return "" | |||
| } | |||
| func GrampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrainJobForm) { | |||
| displayJobName := form.DisplayJobName | |||
| jobName := util.ConvertDisplayJobNameToJobName(displayJobName) | |||
| @@ -281,10 +330,18 @@ func GrampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrain | |||
| engineName := form.EngineName | |||
| if !jobNamePattern.MatchString(displayJobName) { | |||
| grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplGrampusTrainJobNPUNew, &form) | |||
| return | |||
| } | |||
| errStr := checkSpecialPool(ctx, "NPU") | |||
| if errStr != "" { | |||
| grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU) | |||
| ctx.RenderWithErr(errStr, tplGrampusTrainJobGPUNew, &form) | |||
| return | |||
| } | |||
| //check count limit | |||
| count, err := models.GetGrampusCountByUserID(ctx.User.ID, string(models.JobTypeTrain), models.NPUResource) | |||
| if err != nil { | |||
| @@ -607,7 +664,12 @@ func GrampusGetLog(ctx *context.Context) { | |||
| func generateCommand(repoName, processorType, codeRemotePath, dataRemotePath, bootFile, paramSrc, outputRemotePath, datasetName string) (string, error) { | |||
| var command string | |||
| command += grampus.CommandPrepareScript | |||
| workDir := grampus.NpuWorkDir | |||
| if processorType == grampus.ProcessorTypeGPU { | |||
| workDir = grampus.GpuWorkDir | |||
| } | |||
| command += "pwd;cd " + workDir + grampus.CommandPrepareScript | |||
| //download code & dataset | |||
| if processorType == grampus.ProcessorTypeNPU { | |||
| commandDownload := "./downloader_for_obs " + setting.Bucket + " " + codeRemotePath + " " + grampus.CodeArchiveName + " " + dataRemotePath + " " + datasetName + ";" | |||
| @@ -626,7 +688,7 @@ func generateCommand(repoName, processorType, codeRemotePath, dataRemotePath, bo | |||
| if strings.HasSuffix(datasetName, ".tar.gz") { | |||
| toolUnzip = "tar -zxvf " | |||
| } | |||
| commandUnzip := "cd /cache/code;unzip -q master.zip;echo \"start to unzip dataset\";cd /cache/dataset;" + toolUnzip + datasetName + ";" | |||
| commandUnzip := "cd " + workDir + "code;unzip -q master.zip;echo \"start to unzip dataset\";cd " + workDir + "dataset;" + toolUnzip + datasetName + ";" | |||
| command += commandUnzip | |||
| //check unzip result | |||
| @@ -655,7 +717,7 @@ func generateCommand(repoName, processorType, codeRemotePath, dataRemotePath, bo | |||
| } | |||
| } | |||
| commandCode := "cd /cache/code/" + strings.ToLower(repoName) + ";python " + bootFile + paramCode + ";" | |||
| commandCode := "cd " + workDir + "code/" + strings.ToLower(repoName) + ";python " + bootFile + paramCode + ";" | |||
| command += commandCode | |||
| //get exec result | |||
| @@ -664,10 +726,10 @@ func generateCommand(repoName, processorType, codeRemotePath, dataRemotePath, bo | |||
| //upload models | |||
| if processorType == grampus.ProcessorTypeNPU { | |||
| commandUpload := "cd /cache/script_for_grampus/;./uploader_for_obs " + setting.Bucket + " " + outputRemotePath + " " + "/cache/output/;" | |||
| commandUpload := "cd " + workDir + "script_for_grampus/;./uploader_for_obs " + setting.Bucket + " " + outputRemotePath + " " + workDir + "output/;" | |||
| command += commandUpload | |||
| } else if processorType == grampus.ProcessorTypeGPU { | |||
| commandUpload := "cd /cache/script_for_grampus/;./uploader_for_minio " + setting.Grampus.Env + " " + outputRemotePath + " " + "/cache/output/;" | |||
| commandUpload := "cd " + workDir + "script_for_grampus/;./uploader_for_minio " + setting.Grampus.Env + " " + outputRemotePath + " " + workDir + "output/;" | |||
| command += commandUpload | |||
| } | |||
| @@ -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, 31} | |||
| 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, 31, 32, 33} | |||
| type ClientsManager struct { | |||
| Clients *orderedmap.OrderedMap | |||
| @@ -102,7 +102,7 @@ | |||
| </a> | |||
| {{else if eq .JobType "TRAIN"}} | |||
| <a class="title" | |||
| href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts{{end}}/train-job/{{$JobID}}" | |||
| href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .Cloudbrain.Type 0}}/cloudbrain{{else if eq .Cloudbrain.Type 1}}/modelarts{{else if eq .Cloudbrain.Type 2}}/grampus{{end}}/train-job/{{$JobID}}" | |||
| title="{{.DisplayJobName}}" style="font-size: 14px;"> | |||
| <span class="fitted" | |||
| style="width: 90%;vertical-align: middle;">{{.DisplayJobName}}</span> | |||
| @@ -204,7 +204,7 @@ | |||
| {{else}} | |||
| <a style="padding: 0.5rem 1rem;" id="ai-stop-{{$JobID}}" | |||
| class="ui basic ai_stop_version {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED" "SUCCEEDED" "STOPPED"}}disabled {{else}} blue {{end}}button" | |||
| data-repopath="{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain/train-job{{else}}/modelarts/{{if eq .JobType "INFERENCE"}}inference-job{{else}}train-job{{end}}{{end}}" | |||
| data-repopath="{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .Cloudbrain.Type 0}}/cloudbrain/train-job{{else if eq .Cloudbrain.Type 2}}/grampus/train-job{{else}}/modelarts/{{if eq .JobType "INFERENCE"}}inference-job{{else}}train-job{{end}}{{end}}" | |||
| data-jobid="{{$JobID}}" data-version="{{.VersionName}}"> | |||
| {{$.i18n.Tr "repo.stop"}} | |||
| </a> | |||
| @@ -212,7 +212,7 @@ | |||
| </div> | |||
| <!-- 删除任务 --> | |||
| <form class="ui compact buttons" id="delForm-{{$JobID}}" | |||
| action='{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .JobType "BENCHMARK"}}/cloudbrain/benchmark{{else if or (eq .JobType "SNN4IMAGENET") (eq .JobType "BRAINSCORE")}}/cloudbrain{{else if eq .JobType "DEBUG"}}{{if eq .ComputeResource "NPU"}}/modelarts/notebook{{else}}/cloudbrain{{end}}{{else if eq .JobType "TRAIN"}}{{if eq .ComputeResource "NPU"}}/modelarts/notebook{{else}}/cloudbrain{{end}}/train-job{{end}}/{{$JobID}}/del?isadminpage=true' | |||
| action='{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .JobType "BENCHMARK"}}/cloudbrain/benchmark{{else if or (eq .JobType "SNN4IMAGENET") (eq .JobType "BRAINSCORE")}}/cloudbrain{{else if eq .JobType "DEBUG"}}{{if eq .ComputeResource "NPU"}}/modelarts/notebook{{else}}/cloudbrain{{end}}{{else if eq .JobType "TRAIN"}}{{if eq .Cloudbrain.Type 1}}/modelarts/train-job{{else if eq .Cloudbrain.Type 0}}/cloudbrain/train-job{{else if eq .Cloudbrain.Type 2}}/grampus/train-job{{end}}{{end}}/{{$JobID}}/del?isadminpage=true' | |||
| method="post"> | |||
| {{$.CsrfTokenHtml}} | |||
| <a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="ai-delete-{{$JobID}}" | |||
| @@ -18,7 +18,7 @@ | |||
| .ui.list .list>.item>img.image+.content, | |||
| .ui.list>.item>img.image+.content { | |||
| width: calc(100% - 30px); | |||
| width: calc(100% - 34px); | |||
| margin-left: 0; | |||
| } | |||
| @@ -107,9 +107,9 @@ | |||
| {{range .Repos}} | |||
| <div class="item"> | |||
| {{if .RelAvatarLink}} | |||
| <img class="ui avatar image" src="{{.RelAvatarLink}}"> | |||
| <img class="ui avatar image" style="width: 28px;height: 28px;" src="{{.RelAvatarLink}}"> | |||
| {{else}} | |||
| <img class="ui avatar image" avatar="{{.Owner.Name}}"> | |||
| <img class="ui avatar image" style="width: 28px;height: 28px;" avatar="{{.Owner.Name}}"> | |||
| {{end}} | |||
| <div class="content"> | |||
| <div class="ui header"> | |||
| @@ -89,23 +89,23 @@ | |||
| <svg class="svg" sxmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"></path></svg> | |||
| {{.i18n.Tr "cloudbrain.resource_cluster_openi"}} | |||
| </a> | |||
| <a class="active item" href="{{.RepoLink}}/grampus/train-job/npu/create"> | |||
| <svg class="svg" sxmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"></path></svg> | |||
| {{.i18n.Tr "cloudbrain.resource_cluster_c2net"}} | |||
| </a> | |||
| <a class="active item" href="{{.RepoLink}}/grampus/train-job/ {{if.NPUEnabled}}npu{{else}}gpu{{end}}/create"> | |||
| <svg class="svg" sxmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"></path></svg> | |||
| {{.i18n.Tr "cloudbrain.resource_cluster_c2net"}}(Beta) | |||
| </a> | |||
| </div> | |||
| </div> | |||
| <div class="required unite min_title inline field"> | |||
| <label style="font-weight: normal;">{{.i18n.Tr "cloudbrain.compute_resource"}}</label> | |||
| <div class="ui blue mini menu compact selectcloudbrain"> | |||
| <a class="active item" href="{{.RepoLink}}/grampus/train-job/gpu/create"> | |||
| <a {{if.GPUEnabled}}class="active item" href="{{.RepoLink}}/grampus/train-job/gpu/create"{{else}}href="javascript:return false;" class="item disabled" {{end}}> | |||
| <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"> | |||
| <path fill="none" d="M0 0h24v24H0z"/> | |||
| <path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/> | |||
| </svg> | |||
| CPU/GPU | |||
| </a> | |||
| <a class="item" href="{{.RepoLink}}/grampus/train-job/npu/create"> | |||
| <a {{if.NPUEnabled}}class="item" href="{{.RepoLink}}/grampus/train-job/npu/create"{{else}}href="javascript:return false;" class="item disabled" {{end}} > | |||
| <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"> | |||
| <path fill="none" d="M0 0h24v24H0z"/> | |||
| <path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/> | |||
| @@ -167,7 +167,7 @@ | |||
| {{template "custom/select_dataset_train" .}} | |||
| <span class="tooltips" style="margin-left: 11.5rem;margin-bottom: 2rem;">{{.i18n.Tr "repo.grampus.dataset_path_rule"}}</span> | |||
| <span class="tooltips" style="margin-left: 11.5rem;margin-bottom: 2rem;">{{.i18n.Tr "repo.grampus.gpu_dataset_path_rule"}}</span> | |||
| <div class="inline unite min_title field"> | |||
| <label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.run_parameter"}}</label> | |||
| <span id="add_run_para" style="margin-left: 0.5rem;cursor:pointer;color: rgba(3, 102, 214, 100);font-size: 14px;line-height: 26px;font-family: SourceHanSansSC-medium;"><i class="plus square outline icon"></i>{{.i18n.Tr "repo.modelarts.train_job.add_run_parameter"}}</span> | |||
| @@ -85,23 +85,23 @@ | |||
| <svg class="svg" sxmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"></path></svg> | |||
| {{.i18n.Tr "cloudbrain.resource_cluster_openi"}} | |||
| </a> | |||
| <a class="active item" href="{{.RepoLink}}/grampus/train-job/npu/create"> | |||
| <svg class="svg" sxmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"></path></svg> | |||
| {{.i18n.Tr "cloudbrain.resource_cluster_c2net"}}(Beta) | |||
| <a class="active item" href="{{.RepoLink}}/grampus/train-job/ {{if.NPUEnabled}}npu{{else}}gpu{{end}}/create"> | |||
| <svg class="svg" sxmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"></path></svg> | |||
| {{.i18n.Tr "cloudbrain.resource_cluster_c2net"}}(Beta) | |||
| </a> | |||
| </div> | |||
| </div> | |||
| <div class="required unite min_title inline field"> | |||
| <label style="font-weight: normal;">{{.i18n.Tr "cloudbrain.compute_resource"}}</label> | |||
| <div class="ui blue mini menu compact selectcloudbrain"> | |||
| <!--a class="item" href="{{.RepoLink}}/grampus/train-job/gpu/create" type="hidden"> | |||
| <a {{if.GPUEnabled}}class="item" href="{{.RepoLink}}/grampus/train-job/gpu/create"{{else}}href="javascript:return false;" class="item disabled" {{end}}> | |||
| <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"> | |||
| <path fill="none" d="M0 0h24v24H0z"/> | |||
| <path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/> | |||
| </svg> | |||
| CPU/GPU | |||
| </a--> | |||
| <a class="active item" href="{{.RepoLink}}/grampus/train-job/npu/create"> | |||
| </a> | |||
| <a {{if.NPUEnabled}}class="active item" href="{{.RepoLink}}/grampus/train-job/npu/create"{{else}}href="javascript:return false;" class="item disabled" {{end}} > | |||
| <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"> | |||
| <path fill="none" d="M0 0h24v24H0z"/> | |||
| <path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/> | |||
| @@ -1,74 +1,100 @@ | |||
| /** | |||
| * LetterAvatar | |||
| * | |||
| * | |||
| * Artur Heinze | |||
| * Create Letter avatar based on Initials | |||
| * based on https://gist.github.com/leecrossley/6027780 | |||
| */ | |||
| (function(w, d){ | |||
| function LetterAvatar (name, size, color) { | |||
| name = name || ''; | |||
| size = size || 60; | |||
| var colours = [ | |||
| "#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50", | |||
| "#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d" | |||
| ], | |||
| nameSplit = String(name).split(' '), | |||
| initials, charIndex, colourIndex, canvas, context, dataURI; | |||
| if (nameSplit.length == 1) { | |||
| initials = nameSplit[0] ? nameSplit[0].charAt(0):'?'; | |||
| } else { | |||
| initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0); | |||
| } | |||
| if (w.devicePixelRatio) { | |||
| size = (size * w.devicePixelRatio); | |||
| } | |||
| charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64; | |||
| colourIndex = charIndex % 20; | |||
| canvas = d.createElement('canvas'); | |||
| canvas.width = size; | |||
| canvas.height = size; | |||
| context = canvas.getContext("2d"); | |||
| context.fillStyle = color ? color : colours[colourIndex - 1]; | |||
| context.fillRect (0, 0, canvas.width, canvas.height); | |||
| context.font = Math.round(canvas.width/2)+"px 'Microsoft Yahei'"; | |||
| context.textAlign = "center"; | |||
| context.fillStyle = "#FFF"; | |||
| context.fillText(initials, size / 2, size / 1.5); | |||
| dataURI = canvas.toDataURL(); | |||
| canvas = null; | |||
| return dataURI; | |||
| (function (w, d) { | |||
| function LetterAvatar(name, size, color) { | |||
| name = name || ""; | |||
| size = size || 60; | |||
| var colours = [ | |||
| "#1abc9c", | |||
| "#2ecc71", | |||
| "#3498db", | |||
| "#9b59b6", | |||
| "#34495e", | |||
| "#16a085", | |||
| "#27ae60", | |||
| "#2980b9", | |||
| "#8e44ad", | |||
| "#2c3e50", | |||
| "#f1c40f", | |||
| "#e67e22", | |||
| "#e74c3c", | |||
| "#00bcd4", | |||
| "#95a5a6", | |||
| "#f39c12", | |||
| "#d35400", | |||
| "#c0392b", | |||
| "#bdc3c7", | |||
| "#7f8c8d", | |||
| ], | |||
| nameSplit = String(name).split(" "), | |||
| initials, | |||
| charIndex, | |||
| colourIndex, | |||
| canvas, | |||
| context, | |||
| dataURI; | |||
| if (nameSplit.length == 1) { | |||
| initials = nameSplit[0] ? nameSplit[0].charAt(0) : "?"; | |||
| } else { | |||
| initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0); | |||
| } | |||
| let initials1 = initials.toUpperCase(); | |||
| if (w.devicePixelRatio) { | |||
| size = size * w.devicePixelRatio; | |||
| } | |||
| LetterAvatar.transform = function() { | |||
| Array.prototype.forEach.call(d.querySelectorAll('img[avatar]'), function(img, name, color) { | |||
| name = img.getAttribute('avatar'); | |||
| color = img.getAttribute('color'); | |||
| img.src = LetterAvatar(name, img.getAttribute('width'), color); | |||
| img.removeAttribute('avatar'); | |||
| img.setAttribute('alt', name); | |||
| }); | |||
| }; | |||
| // AMD support | |||
| if (typeof define === 'function' && define.amd) { | |||
| define(function () { return LetterAvatar; }); | |||
| charIndex = (initials == "?" ? 72 : initials.charCodeAt(0)) - 64; | |||
| colourIndex = charIndex % 20; | |||
| canvas = d.createElement("canvas"); | |||
| canvas.width = size; | |||
| canvas.height = size; | |||
| context = canvas.getContext("2d"); | |||
| context.fillStyle = color ? color : colours[colourIndex - 1]; | |||
| context.fillRect(0, 0, canvas.width, canvas.height); | |||
| context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'"; | |||
| context.textAlign = "center"; | |||
| context.fillStyle = "#FFF"; | |||
| context.fillText(initials1, size / 2, size / 1.5); | |||
| dataURI = canvas.toDataURL(); | |||
| canvas = null; | |||
| return dataURI; | |||
| } | |||
| LetterAvatar.transform = function () { | |||
| Array.prototype.forEach.call( | |||
| d.querySelectorAll("img[avatar]"), | |||
| function (img, name, color) { | |||
| name = img.getAttribute("avatar"); | |||
| color = img.getAttribute("color"); | |||
| img.src = LetterAvatar(name, img.getAttribute("width"), color); | |||
| img.removeAttribute("avatar"); | |||
| img.setAttribute("alt", name); | |||
| } | |||
| ); | |||
| }; | |||
| // AMD support | |||
| if (typeof define === "function" && define.amd) { | |||
| define(function () { | |||
| return LetterAvatar; | |||
| }); | |||
| // CommonJS and Node.js module support. | |||
| } else if (typeof exports !== 'undefined') { | |||
| // Support Node.js specific `module.exports` (which can be a function) | |||
| if (typeof module != 'undefined' && module.exports) { | |||
| exports = module.exports = LetterAvatar; | |||
| } | |||
| // But always support CommonJS module 1.1.1 spec (`exports` cannot be a function) | |||
| exports.LetterAvatar = LetterAvatar; | |||
| } else { | |||
| window.LetterAvatar = LetterAvatar; | |||
| d.addEventListener('DOMContentLoaded', function(event) { | |||
| LetterAvatar.transform(); | |||
| }); | |||
| } else if (typeof exports !== "undefined") { | |||
| // Support Node.js specific `module.exports` (which can be a function) | |||
| if (typeof module != "undefined" && module.exports) { | |||
| exports = module.exports = LetterAvatar; | |||
| } | |||
| })(window, document); | |||
| // But always support CommonJS module 1.1.1 spec (`exports` cannot be a function) | |||
| exports.LetterAvatar = LetterAvatar; | |||
| } else { | |||
| window.LetterAvatar = LetterAvatar; | |||
| d.addEventListener("DOMContentLoaded", function (event) { | |||
| LetterAvatar.transform(); | |||
| }); | |||
| } | |||
| })(window, document); | |||