Browse Source

Merge branch 'V20211018' of git.openi.org.cn:OpenI/aiforge into repo-statistic

tags/v1.21.12.1
lewis 4 years ago
parent
commit
a3c23fbdc6
29 changed files with 3036 additions and 306 deletions
  1. +1
    -1
      README.md
  2. +10
    -3
      models/cloudbrain.go
  3. +3
    -1
      models/models.go
  4. +5
    -4
      models/repo_watch.go
  5. +8
    -5
      models/star.go
  6. +12
    -0
      models/user.go
  7. +356
    -0
      models/user_business_analysis.go
  8. +6
    -3
      models/user_follow.go
  9. +9
    -0
      modules/git/repo_commit.go
  10. +3
    -1
      options/locale/locale_en-US.ini
  11. +2
    -0
      options/locale/locale_zh-CN.ini
  12. +1653
    -0
      public/img/icons.svg
  13. BIN
      public/img/loading.gif
  14. +2
    -1
      routers/repo/cloudbrain.go
  15. +55
    -0
      routers/repo/user_data_analysis.go
  16. +15
    -0
      routers/repo/wiki.go
  17. +1
    -1
      templates/base/head_navbar.tmpl
  18. +1
    -1
      templates/base/head_navbar_home.tmpl
  19. +27
    -7
      templates/repo/cloudbrain/index.tmpl
  20. +35
    -46
      templates/repo/datasets/dataset_list.tmpl
  21. +1
    -1
      templates/repo/datasets/index.tmpl
  22. +61
    -7
      templates/repo/home.tmpl
  23. +1
    -1
      templates/repo/modelarts/index.tmpl
  24. +468
    -0
      web_src/js/components/EditTopics.vue
  25. +4
    -5
      web_src/js/components/MinioUploader.vue
  26. +5
    -2
      web_src/js/components/ObsUploader.vue
  27. +255
    -214
      web_src/js/index.js
  28. +15
    -2
      web_src/less/_dataset.less
  29. +22
    -0
      web_src/less/openi.less

+ 1
- 1
README.md View File

@@ -2,7 +2,7 @@

<h1><img src="public/img/favicon.png" alt="logo" width="30" height="30">AiForge - 启智AI开发协作平台</h1>

[![release](https://img.shields.io/badge/release-1.21.10.1-blue)](https://git.openi.org.cn/OpenI/aiforge/releases/latest)
[![release](https://img.shields.io/badge/release-1.21.9.2-blue)](https://git.openi.org.cn/OpenI/aiforge/releases/latest)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)




+ 10
- 3
models/cloudbrain.go View File

@@ -65,6 +65,11 @@ type Cloudbrain struct {
Repo *Repository `xorm:"-"`
}

type CloudbrainInfo struct {
Cloudbrain `xorm:"extends"`
User `xorm:"extends"`
}

type CloudBrainLoginResult struct {
Code string
Msg string
@@ -523,7 +528,7 @@ type NotebookDelResult struct {
InstanceID string `json:"instance_id"`
}

func Cloudbrains(opts *CloudbrainsOptions) ([]*Cloudbrain, int64, error) {
func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
sess := x.NewSession()
defer sess.Close()

@@ -583,8 +588,10 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*Cloudbrain, int64, error) {
}

sess.OrderBy("cloudbrain.created_unix DESC")
cloudbrains := make([]*Cloudbrain, 0, setting.UI.IssuePagingNum)
if err := sess.Where(cond).Find(&cloudbrains); err != nil {
cloudbrains := make([]*CloudbrainInfo, 0, setting.UI.IssuePagingNum)
if err := sess.Table(&Cloudbrain{}).Where(cond).
Join("left", "`user`", "cloudbrain.user_id = `user`.id").
Find(&cloudbrains); err != nil {
return nil, 0, fmt.Errorf("Find: %v", err)
}
sess.Close()


+ 3
- 1
models/models.go View File

@@ -136,7 +136,9 @@ func init() {
)

tablesStatistic = append(tablesStatistic,
new(FileChunk))
new(FileChunk),
new(UserBusinessAnalysis),
)

gonicNames := []string{"SSL", "UID"}
for _, name := range gonicNames {


+ 5
- 4
models/repo_watch.go View File

@@ -26,10 +26,11 @@ const (

// Watch is connection request for receiving repository notification.
type Watch struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(watch)"`
RepoID int64 `xorm:"UNIQUE(watch)"`
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(watch)"`
RepoID int64 `xorm:"UNIQUE(watch)"`
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
CreatedUnix int64 `xorm:"created"`
}

// getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found


+ 8
- 5
models/star.go View File

@@ -4,11 +4,14 @@

package models

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

// Star represents a starred repo by an user.
type Star struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"UNIQUE(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"UNIQUE(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}

// StarRepo or unstar repository.
@@ -39,7 +42,7 @@ func StarRepo(userID, repoID int64, star bool) error {
return nil
}

if _, err := sess.Delete(&Star{0, userID, repoID}); err != nil {
if _, err := sess.Delete(&Star{0, userID, repoID, 0}); err != nil {
return err
}
if _, err := sess.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID); err != nil {
@@ -59,7 +62,7 @@ func IsStaring(userID, repoID int64) bool {
}

func isStaring(e Engine, userID, repoID int64) bool {
has, _ := e.Get(&Star{0, userID, repoID})
has, _ := e.Get(&Star{0, userID, repoID, 0})
return has
}



+ 12
- 0
models/user.go View File

@@ -1556,6 +1556,18 @@ func GetUserByActivateEmail(email string) (*User, error) {
if len(users) >= 1 {
return &users[0],nil
}else {
// Finally, if email address is the protected email address:用户邮件地址设置为隐藏电子邮件地址
if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) {
username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress))
user := &User{LowerName: username}
has, err := ctx.e.Get(user)
if err != nil {
return nil, err
}
if has {
return user, nil
}
}
return nil, errors.New("cannot find user by email")
}
}


+ 356
- 0
models/user_business_analysis.go View File

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

import (
"fmt"
"time"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
)

type UserBusinessAnalysis struct {
ID int64 `xorm:"pk"`

CountDate int64 `xorm:"pk"`

//action :ActionMergePullRequest // 11
CodeMergeCount int `xorm:"NOT NULL DEFAULT 0"`

//action :ActionCommitRepo // 5
CommitCount int `xorm:"NOT NULL DEFAULT 0"`

//action :ActionCommentIssue // 10
IssueCount int `xorm:"NOT NULL DEFAULT 0"`

//comment table current date
CommentCount int `xorm:"NOT NULL DEFAULT 0"`

//watch table current date
FocusRepoCount int `xorm:"NOT NULL DEFAULT 0"`

//star table current date
StarRepoCount int `xorm:"NOT NULL DEFAULT 0"`

//follow table
WatchedCount int `xorm:"NOT NULL DEFAULT 0"`

// user table
GiteaAgeMonth int `xorm:"NOT NULL DEFAULT 0"`

//
CommitCodeSize int `xorm:"NOT NULL DEFAULT 0"`

//attachement table
CommitDatasetSize int `xorm:"NOT NULL DEFAULT 0"`

//0
CommitModelCount int `xorm:"NOT NULL DEFAULT 0"`

//issue, issueassignees
SolveIssueCount int `xorm:"NOT NULL DEFAULT 0"`

//baike
EncyclopediasCount int `xorm:"NOT NULL DEFAULT 0"`

//user
RegistDate timeutil.TimeStamp `xorm:"NOT NULL"`

//user
Email string `xorm:"NOT NULL"`

//user
Name string `xorm:"NOT NULL"`
}

func CountData(wikiCountMap map[string]int) {
log.Info("start to count other user info data")
sess := x.NewSession()
defer sess.Close()
sess.Select("`user`.*").Table("user")
userList := make([]*User, 0)
sess.Find(&userList)

currentTimeNow := time.Now()
log.Info("current time:" + currentTimeNow.Format("2006-01-02 15:04:05"))

yesterday := currentTimeNow.AddDate(0, 0, -1)
startTime := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, yesterday.Location())
start_unix := startTime.Unix()
log.Info("DB query time:" + startTime.Format("2006-01-02 15:04:05"))

endTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location())
end_unix := endTime.Unix()

CountDate := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 1, 0, 0, currentTimeNow.Location())

CodeMergeCountMap := queryAction(start_unix, end_unix, 11)
CommitCountMap := queryAction(start_unix, end_unix, 5)
IssueCountMap := queryAction(start_unix, end_unix, 10)

CommentCountMap := queryComment(start_unix, end_unix)
FocusRepoCountMap := queryWatch(start_unix, end_unix)
StarRepoCountMap := queryStar(start_unix, end_unix)
WatchedCountMap := queryFollow(start_unix, end_unix)

CommitCodeSizeMap, err := GetAllUserKPIStats()
if err != nil {
log.Info("query commit code errr.")
} else {
log.Info("query commit code size, len=" + fmt.Sprint(len(CommitCodeSizeMap)))
}
CommitDatasetSizeMap := queryDatasetSize(start_unix, end_unix)
SolveIssueCountMap := querySolveIssue(start_unix, end_unix)

for i, userRecord := range userList {
var dateRecord UserBusinessAnalysis
dateRecord.ID = userRecord.ID
log.Info("i=" + fmt.Sprint(i) + " userName=" + userRecord.Name)
dateRecord.CountDate = CountDate.Unix()
dateRecord.Email = userRecord.Email
dateRecord.RegistDate = userRecord.CreatedUnix
dateRecord.Name = userRecord.Name
dateRecord.GiteaAgeMonth = subMonth(currentTimeNow, userRecord.CreatedUnix.AsTime())
if _, ok := CodeMergeCountMap[dateRecord.ID]; !ok {
dateRecord.CodeMergeCount = 0
} else {
dateRecord.CodeMergeCount = CodeMergeCountMap[dateRecord.ID]
}

if _, ok := CommitCountMap[dateRecord.ID]; !ok {
dateRecord.CommitCount = 0
} else {
dateRecord.CommitCount = CommitCountMap[dateRecord.ID]
}

if _, ok := IssueCountMap[dateRecord.ID]; !ok {
dateRecord.IssueCount = 0
} else {
dateRecord.IssueCount = IssueCountMap[dateRecord.ID]
}

if _, ok := CommentCountMap[dateRecord.ID]; !ok {
dateRecord.CommentCount = 0
} else {
dateRecord.CommentCount = CommentCountMap[dateRecord.ID]
}

if _, ok := FocusRepoCountMap[dateRecord.ID]; !ok {
dateRecord.FocusRepoCount = 0
} else {
dateRecord.FocusRepoCount = FocusRepoCountMap[dateRecord.ID]
}

if _, ok := StarRepoCountMap[dateRecord.ID]; !ok {
dateRecord.StarRepoCount = 0
} else {
dateRecord.StarRepoCount = StarRepoCountMap[dateRecord.ID]
}

if _, ok := WatchedCountMap[dateRecord.ID]; !ok {
dateRecord.WatchedCount = 0
} else {
dateRecord.WatchedCount = WatchedCountMap[dateRecord.ID]
}

if _, ok := CommitCodeSizeMap[dateRecord.Email]; !ok {
dateRecord.CommitCodeSize = 0
} else {
dateRecord.CommitCodeSize = int(CommitCodeSizeMap[dateRecord.Email].CommitLines)
}

if _, ok := CommitDatasetSizeMap[dateRecord.ID]; !ok {
dateRecord.CommitDatasetSize = 0
} else {
dateRecord.CommitDatasetSize = CommitDatasetSizeMap[dateRecord.ID]
}

if _, ok := SolveIssueCountMap[dateRecord.ID]; !ok {
dateRecord.SolveIssueCount = 0
} else {
dateRecord.SolveIssueCount = SolveIssueCountMap[dateRecord.ID]
}

if _, ok := wikiCountMap[dateRecord.Name]; !ok {
dateRecord.EncyclopediasCount = 0
} else {
dateRecord.EncyclopediasCount = wikiCountMap[dateRecord.Name]
}

dateRecord.CommitModelCount = 0

statictisSess := xStatistic.NewSession()
defer statictisSess.Close()
statictisSess.Insert(&dateRecord)
}

}

func querySolveIssue(start_unix int64, end_unix int64) map[int64]int {
//select issue_assignees.* from issue_assignees,issue where issue.is_closed=true and issue.id=issue_assignees.issue_id
sess := x.NewSession()
defer sess.Close()
sess.Select("issue_assignees.*").Table("issue_assignees").
Join("inner", "issue", "issue.id=issue_assignees.issue_id").
Where("issue.is_closed=true and issue.closed_unix>=" + fmt.Sprint(start_unix) + " and issue.closed_unix<=" + fmt.Sprint(end_unix))
issueAssigneesList := make([]*IssueAssignees, 0)
sess.Find(&issueAssigneesList)
resultMap := make(map[int64]int)
log.Info("query IssueAssignees size=" + fmt.Sprint(len(issueAssigneesList)))
for _, issueAssigneesRecord := range issueAssigneesList {
if _, ok := resultMap[issueAssigneesRecord.AssigneeID]; !ok {
resultMap[issueAssigneesRecord.AssigneeID] = 1
} else {
resultMap[issueAssigneesRecord.AssigneeID] += 1
}
}
return resultMap

}

func queryAction(start_unix int64, end_unix int64, actionType int64) map[int64]int {
sess := x.NewSession()
defer sess.Close()
sess.Select("id,user_id,op_type,act_user_id").Table("action").Where("op_type=" + fmt.Sprint(actionType) + " and created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
actionList := make([]*Action, 0)
sess.Find(&actionList)
resultMap := make(map[int64]int)
log.Info("query action size=" + fmt.Sprint(len(actionList)))
for _, actionRecord := range actionList {
if _, ok := resultMap[actionRecord.UserID]; !ok {
resultMap[actionRecord.UserID] = 1
} else {
resultMap[actionRecord.UserID] += 1
}
}
return resultMap
}

func queryComment(start_unix int64, end_unix int64) map[int64]int {

sess := x.NewSession()
defer sess.Close()
sess.Select("id,type,poster_id").Table("comment").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
commentList := make([]*Comment, 0)
sess.Find(&commentList)
resultMap := make(map[int64]int)
log.Info("query Comment size=" + fmt.Sprint(len(commentList)))
for _, commentRecord := range commentList {
if _, ok := resultMap[commentRecord.PosterID]; !ok {
resultMap[commentRecord.PosterID] = 1
} else {
resultMap[commentRecord.PosterID] += 1
}
}
return resultMap
}

func queryWatch(start_unix int64, end_unix int64) map[int64]int {

sess := x.NewSession()
defer sess.Close()
sess.Select("id,user_id,repo_id").Table("watch").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
watchList := make([]*Watch, 0)
sess.Find(&watchList)
resultMap := make(map[int64]int)
log.Info("query Watch size=" + fmt.Sprint(len(watchList)))
for _, watchRecord := range watchList {
if _, ok := resultMap[watchRecord.UserID]; !ok {
resultMap[watchRecord.UserID] = 1
} else {
resultMap[watchRecord.UserID] += 1
}
}
return resultMap

}

func queryStar(start_unix int64, end_unix int64) map[int64]int {

sess := x.NewSession()
defer sess.Close()
sess.Select("id,uid,repo_id").Table("star").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
starList := make([]*Star, 0)
sess.Find(&starList)
resultMap := make(map[int64]int)
log.Info("query Star size=" + fmt.Sprint(len(starList)))
for _, starRecord := range starList {
if _, ok := resultMap[starRecord.UID]; !ok {
resultMap[starRecord.UID] = 1
} else {
resultMap[starRecord.UID] += 1
}
}
return resultMap

}

func queryFollow(start_unix int64, end_unix int64) map[int64]int {

sess := x.NewSession()
defer sess.Close()
sess.Select("id,user_id,follow_id").Table("follow").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
followList := make([]*Follow, 0)
sess.Find(&followList)
resultMap := make(map[int64]int)
log.Info("query Follow size=" + fmt.Sprint(len(followList)))
for _, followRecord := range followList {
if _, ok := resultMap[followRecord.UserID]; !ok {
resultMap[followRecord.UserID] = 1
} else {
resultMap[followRecord.UserID] += 1
}
}
return resultMap
}

func queryDatasetSize(start_unix int64, end_unix int64) map[int64]int {
sess := x.NewSession()
defer sess.Close()
sess.Select("id,uploader_id,size").Table("attachment").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
attachmentList := make([]*Attachment, 0)
sess.Find(&attachmentList)
resultMap := make(map[int64]int)
log.Info("query Attachment size=" + fmt.Sprint(len(attachmentList)))
for _, attachRecord := range attachmentList {
if _, ok := resultMap[attachRecord.UploaderID]; !ok {
resultMap[attachRecord.UploaderID] = int(attachRecord.Size / (1024 * 1024)) //MB
} else {
resultMap[attachRecord.UploaderID] += int(attachRecord.Size / (1024 * 1024)) //MB
}
}
return resultMap

}

func subMonth(t1, t2 time.Time) (month int) {
y1 := t1.Year()
y2 := t2.Year()
m1 := int(t1.Month())
m2 := int(t2.Month())
d1 := t1.Day()
d2 := t2.Day()

yearInterval := y1 - y2
// 如果 d1的 月-日 小于 d2的 月-日 那么 yearInterval-- 这样就得到了相差的年数
if m1 < m2 || m1 == m2 && d1 < d2 {
yearInterval--
}
// 获取月数差值
monthInterval := (m1 + 12) - m2
if d1 < d2 {
monthInterval--
}
monthInterval %= 12
month = yearInterval*12 + monthInterval
return month
}

func QueryAllRepo() []*Repository {
sess := x.NewSession()
defer sess.Close()
sess.Select("*").Table("repository")
repositoryList := make([]*Repository, 0)
sess.Find(&repositoryList)

return repositoryList
}

+ 6
- 3
models/user_follow.go View File

@@ -4,11 +4,14 @@

package models

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

// Follow represents relations of user and his/her followers.
type Follow struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(follow)"`
FollowID int64 `xorm:"UNIQUE(follow)"`
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(follow)"`
FollowID int64 `xorm:"UNIQUE(follow)"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}

// IsFollowing returns true if user is following followID.


+ 9
- 0
modules/git/repo_commit.go View File

@@ -206,6 +206,15 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
return commits.Front().Value.(*Commit), nil
}

func (repo *Repository) GetCommitByPathAndDays(relpath string, days int) (*list.List, error) {
stdout, err := NewCommand("log", "-1", prettyLogFormat, "--since="+fmt.Sprint(days)+".days").RunInDirBytes(relpath)
if err != nil {
return nil, err
}

return repo.parsePrettyFormatLogToList(stdout)
}

// CommitsRangeSize the default commits range size
var CommitsRangeSize = 50



+ 3
- 1
options/locale/locale_en-US.ini View File

@@ -573,7 +573,7 @@ authorized_oauth2_applications_description = You've granted access to your perso
revoke_key = Revoke
revoke_oauth2_grant = Revoke Access
revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure?
revoke_oauth2_grant_success = You've revoked access successfully.
revoke_oauth2_grant_success = You have revoked access successfully.

twofa_desc = Two-factor authentication enhances the security of your account.
twofa_is_enrolled = Your account is currently <strong>enrolled</strong> in two-factor authentication.
@@ -770,6 +770,7 @@ cloudbrain_selection = select cloudbrain
cloudbrain_platform_selection = Select the cloudbrain platform you want to use:
confirm_choice = confirm
cloudbran1_tips = Only data in zip format can create cloudbrain tasks
cloudbrain_creator=Creator

template.items = Template Items
template.git_content = Git Content (Default Branch)
@@ -831,6 +832,7 @@ fork = Fork
download_archive = Download Repository

no_desc = No Description
no_label = No labels
quick_guide = Quick Guide
clone_this_repo = Clone this repository
create_new_repo_command = Creating a new repository on the command line


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

@@ -772,6 +772,7 @@ cloudbrain_selection=云脑选择
cloudbrain_platform_selection=选择您准备使用的云脑平台:
confirm_choice=确定
cloudbran1_tips=只有zip格式的数据集才能发起云脑任务
cloudbrain_creator=创建者

template.items=模板选项
template.git_content=Git数据(默认分支)
@@ -833,6 +834,7 @@ fork=派生
download_archive=下载此项目

no_desc=暂无描述
no_label = 暂无标签
quick_guide=快速帮助
clone_this_repo=克隆当前项目
create_new_repo_command=从命令行创建一个新的项目


+ 1653
- 0
public/img/icons.svg
File diff suppressed because it is too large
View File


BIN
public/img/loading.gif View File

Before After
Width: 16  |  Height: 16  |  Size: 2.9 kB

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

@@ -69,7 +69,8 @@ func CloudBrainIndex(ctx *context.Context) {

timestamp := time.Now().Unix()
for i, task := range ciTasks {
if task.Status == string(models.JobRunning) && (timestamp-int64(task.CreatedUnix) > 10) {
log.Info("", task.User.Name)
if task.Status == string(models.JobRunning) && (timestamp-int64(task.Cloudbrain.CreatedUnix) > 10) {
ciTasks[i].CanDebug = true
} else {
ciTasks[i].CanDebug = false


+ 55
- 0
routers/repo/user_data_analysis.go View File

@@ -0,0 +1,55 @@
package repo

import (
"time"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
)

func TimeingCountData() {
//query wiki data
log.Info("start to time count data")
wikiMap := make(map[string]int)

currentTimeNow := time.Now()
log.Info("current time:" + currentTimeNow.Format("2006-01-02 15:04:05"))

yesterday := currentTimeNow.AddDate(0, 0, -1)

repoList := models.QueryAllRepo()
log.Info("start to query wiki data")
for _, repoRecord := range repoList {
wikiPath := models.WikiPath(repoRecord.OwnerName, repoRecord.Name)
time, err := git.GetLatestCommitTime(wikiPath)
if err == nil {
log.Info("last commit time:" + time.Format("2006-01-02 15:04:05") + " wikiPath=" + wikiPath)
if time.After(yesterday) {
wikiRepo, _, err := FindWikiRepoCommitByWikiPath(wikiPath)
if err != nil {
log.Error("wiki not exist. wikiPath=" + wikiPath)
} else {
log.Info("wiki exist, wikiPath=" + wikiPath)
list, err := wikiRepo.GetCommitByPathAndDays(wikiPath, 1)
if err != nil {
log.Info("err,err=v%", err)
} else {
for logEntry := list.Front(); logEntry != nil; logEntry = logEntry.Next() {
commit := logEntry.Value.(*git.Commit)
log.Info("commit msg=" + commit.CommitMessage + " time=" + commit.Committer.When.Format("2006-01-02 15:04:05") + " user=" + commit.Committer.Name)
if _, ok := wikiMap[commit.Committer.Name]; !ok {
wikiMap[commit.Committer.Name] = 1
} else {
wikiMap[commit.Committer.Name] += 1
}
}
}

}
}
}
}
//other user info data
models.CountData(wikiMap)
}

+ 15
- 0
routers/repo/wiki.go View File

@@ -82,6 +82,20 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
return commit.GetTreeEntryByPath(unescapedTarget)
}

func FindWikiRepoCommitByWikiPath(wikiPath string) (*git.Repository, *git.Commit, error) {
wikiRepo, err := git.OpenRepository(wikiPath)
if err != nil {
log.Info("get wiki error.")
return nil, nil, err
}

commit, err := wikiRepo.GetBranchCommit("master")
if err != nil {
return wikiRepo, nil, err
}
return wikiRepo, commit, nil
}

func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath())
if err != nil {
@@ -150,6 +164,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
if !entry.IsRegular() {
continue
}

wikiName, err := wiki_service.FilenameToName(entry.Name())
if err != nil {
if models.IsErrWikiInvalidFileName(err) {


+ 1
- 1
templates/base/head_navbar.tmpl View File

@@ -154,7 +154,7 @@
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}}
</a>
{{end}}
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login?redirect_to={{.Link}}">
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login">
{{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}}
</a>
</div><!-- end anonymous right menu -->


+ 1
- 1
templates/base/head_navbar_home.tmpl View File

@@ -154,7 +154,7 @@
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}}
</a>
{{end}}
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login?redirect_to={{.Link}}">
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login">
{{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}}
</a>
</div><!-- end anonymous right menu -->


+ 27
- 7
templates/repo/cloudbrain/index.tmpl View File

@@ -187,6 +187,12 @@
cursor: pointer;
pointer-events: none;
}
.time-show{
font-size: 10px;
margin-top: 0.4rem;
display: inline-block;
}
</style>

<!-- 弹窗 -->
@@ -256,23 +262,37 @@
<div class="row">

<!-- 任务名 -->
<div class="six wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}">
<div class="five wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}">
<span class="fitted">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted">{{.JobName}}</span>
<span class="fitted text_over" style="width: 90%;">{{.JobName}}</span>
</a>
</div>

<div class="three wide column">
<!--任务状态 -->
<span class="ui compact button job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
<!-- <span class="ui compact button job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
{{.Status}}
</span>
</span> -->
{{if eq .Status "STOPPED"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-stop"></i><span style="margin-left: 0.4em;font-size: 12px;">已停止</span></span>
{{else if eq .Status "RUNNING"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-running"></i><span style="margin-left: 0.4em;font-size: 12px;">运行中</span></span>
{{else if eq .Status "FAILED"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-running"></i><span style="margin-left: 0.4em;font-size: 12px;">运行失败</span></span>
{{else if eq .Status "WAITING"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="showCircle"></i><span style="margin-left: 0.4em;font-size: 12px;">初始化等待</span></span>
{{end}}
<!-- 任务创建时间 -->
<span class="">{{TimeSinceUnix .CreatedUnix $.Lang}}</span>
<span class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>

<div class="seven wide column text right">
<div class="two wide column">
<span style="display: block;">{{$.i18n.Tr "repo.cloudbrain_creator"}}</span><span class="text_over" title="{{.User.Name}}">{{.User.Name}}</span>

</div>
<div class="six wide column text right">
<div class="ui compact buttons" style="margin-right:10px;">
{{if and (ne .Status "WAITING") (ne .JobType "DEBUG")}}
<a class="ui basic button" href="{{$.Link}}/{{.JobID}}/rate" target="_blank">


+ 35
- 46
templates/repo/datasets/dataset_list.tmpl View File

@@ -1,63 +1,52 @@
<style>

</style>
{{if .Attachments}}
{{range .Attachments}}
<div class="ui grid item" id="{{.UUID}}">
<div class="row">
<div class="{{if $.Permission.CanWrite $.UnitTypeDatasets}}five{{else}}nine{{end}} wide column">
<div class="eight wide column">
<span class="ui right">{{.Size | FileSize}}</span>
<a class="title" href="{{.DownloadURL}}?type={{$.Type}}">
<span class="fitted">{{svg "octicon-cloud-download" 16}}</span> {{.Name}}
{{svg "octicon-cloud-download" 16}} {{.Name}}
</a>
</div>
<div class="two wide column">
{{.Size | FileSize}}
</div>
<div class="two wide column">
<span class="ui text center" data-tooltip='{{$.i18n.Tr "dataset.download_count"}}' data-position="bottom right">{{svg "octicon-flame" 16}} {{(.DownloadCount | PrettyNumber)}}</span>
</div>

<div class="one wide column" style="{{if ne $.Type 0}}visibility: hidden;{{end}}">
<span class="ui text center clipboard" data-clipboard-text="{{.DownloadURL}}" data-tooltip='{{$.i18n.Tr "dataset.copy_url"}}' data-clipboard-action="copy">{{svg "octicon-file" 16}}</span>
</div>

<div class="one wide column">
<span class="ui text center clipboard" data-clipboard-text="{{.FileChunk.Md5}}" data-tooltip='{{$.i18n.Tr "dataset.copy_md5"}}' data-clipboard-action="copy">{{svg "octicon-file-binary" 16}}</span>
</div>

<div class="wide column one" style="{{if ne .DecompressState 1}}visibility: hidden;{{end}}">
<a class="ui text center" href="datasets/dirs/{{.UUID}}?type={{$.Type}}" data-tooltip='{{$.i18n.Tr "dataset.directory"}}'>{{svg "octicon-file-directory" 16}}</a>
</div>
{{if $.IsSigned}}
<div class="wide column one" style="{{if ne .DecompressState 1}}visibility: hidden;{{end}}">
<a class="ui text center" href="datasets/label/{{.UUID}}?type={{$.Type}}" data-tooltip='{{$.i18n.Tr "dataset.create_label_task"}}'><i class="fa fa-pencil-square-o" aria-hidden="true"></i></a>
<div class="eight wide column right aligned">
<div class="ui left mini icon buttons">
<span class="ui basic button" data-tooltip='{{$.i18n.Tr "dataset.download_count"}}' data-position="bottom right">{{svg "octicon-flame" 16}} {{(.DownloadCount | PrettyNumber)}}</span>
<span class="ui basic basic button clipboard" data-clipboard-text="{{.DownloadURL}}" data-tooltip='{{$.i18n.Tr "dataset.copy_url"}}' data-clipboard-action="copy"{{if ne $.Type 0}} style="display:none;"{{end}}>{{svg "octicon-file" 16}}</span>
<span class="ui basic basic button clipboard" data-clipboard-text="{{.FileChunk.Md5}}" data-tooltip='{{$.i18n.Tr "dataset.copy_md5"}}' data-clipboard-action="copy">{{svg "octicon-file-binary" 16}}</span>
</div>
{{end}}
{{if not .CanDel}}
<div class="two wide column">
<a class="ui button mini" disabled='true'>{{if .IsPrivate}} {{$.i18n.Tr "dataset.private"}} {{else}} {{$.i18n.Tr "dataset.public"}} {{end}}</a>
{{if ne .DecompressState 0}}
<div class="ui left mini icon buttons">
<a class="ui basic blue button" href="datasets/dirs/{{.UUID}}?type={{$.Type}}" data-tooltip='{{$.i18n.Tr "dataset.directory"}}'>{{svg "octicon-file-directory" 16}}</a>
{{if $.IsSigned}}
<a class="ui basic blue button" href="datasets/label/{{.UUID}}?type={{$.Type}}" data-tooltip='{{$.i18n.Tr "dataset.create_label_task"}}'>{{svg "octicon-pencil" 16}}</a>
{{end}}
</div>
{{else}}
{{if $.Permission.CanWrite $.UnitTypeDatasets}}
{{end}}
{{if not .CanDel}}
<a class="ui right small disabled button">{{$.i18n.Tr "dataset.delete"}}</a>
<span style="margin-right: 10px;line-height: 34px;" class="ui text{{if .IsPrivate}} red{{else}} green{{end}}">{{if .IsPrivate}} {{$.i18n.Tr "dataset.private"}} {{else}} {{$.i18n.Tr "dataset.public"}} {{end}}</span>
{{else}}
{{if $.Permission.CanWrite $.UnitTypeDatasets}}
<a class="ui right small red button" href="javascript:void(0)" data-uuid={{.UUID}} data-dataset-delete data-remove-url="{{AppSubUrl}}/attachments/delete" data-csrf="{{$.CsrfToken}}">{{$.i18n.Tr "dataset.delete"}}</a>
{{if $.Repository.IsPrivate}}
<div class="two wide column">
<a class="ui button mini" disabled='true' data-tooltip='{{$.i18n.Tr "dataset.how_to_public"}}'>{{$.i18n.Tr "dataset.private"}}</a>
</div>
{{ else }}
<div class="two wide column">
<div class="ui buttons mini">
<a class="ui button mini {{if .IsPrivate}}positive active{{end}}" href="javascript:void(0)" data-dataset-status="true-{{.UUID}}" data-csrf="{{$.CsrfToken}}" data-url="{{AppSubUrl}}/attachments/private" data-uuid={{.UUID}} data-private="true" data-is-private={{.IsPrivate}}>{{$.i18n.Tr "dataset.private"}}</a>
<span data-tooltip='{{$.i18n.Tr "dataset.how_to_public"}}' style="margin-right: 10px; line-height: 34px;" class="ui text red">{{$.i18n.Tr "dataset.private"}}</span>
{{else}}
<div class="compact small ui buttons" style="margin-right: 10px;">
<a class="ui button{{if .IsPrivate}} positive active{{end}}" href="javascript:void(0)" data-dataset-status="true-{{.UUID}}" data-csrf="{{$.CsrfToken}}" data-url="{{AppSubUrl}}/attachments/private" data-uuid={{.UUID}} data-private="true" data-is-private={{.IsPrivate}}>{{$.i18n.Tr "dataset.private"}}</a>
<div class="or"></div>
<a class="ui button mini {{if not .IsPrivate}}positive active{{end}}" href="javascript:void(0)" data-dataset-status="false-{{.UUID}}" data-csrf="{{$.CsrfToken}}" data-url="{{AppSubUrl}}/attachments/private" data-uuid={{.UUID}} data-private="false" data-is-private={{.IsPrivate}}>{{$.i18n.Tr "dataset.public"}}</a>
<a class="ui button{{if not .IsPrivate}} positive active{{end}}" href="javascript:void(0)" data-dataset-status="false-{{.UUID}}" data-csrf="{{$.CsrfToken}}" data-url="{{AppSubUrl}}/attachments/private" data-uuid={{.UUID}} data-private="false" data-is-private={{.IsPrivate}}>{{$.i18n.Tr "dataset.public"}}</a>
</div>
</div>
{{end}}
{{else}}
<a class="ui right small disabled button">{{$.i18n.Tr "dataset.delete"}}</a>
<span style="margin-right: 10px;line-height: 34px;" class="ui text{{if .IsPrivate}} red{{else}} green{{end}}">{{if .IsPrivate}} {{$.i18n.Tr "dataset.private"}} {{else}} {{$.i18n.Tr "dataset.public"}} {{end}}</span>
{{end}}
<div class="two wide column right aligned">
<a class="ui red button mini" href="javascript:void(0)" data-uuid={{.UUID}} data-dataset-delete data-remove-url="{{AppSubUrl}}/attachments/delete" data-csrf="{{$.CsrfToken}}">{{$.i18n.Tr "dataset.delete"}}</a>
</div>
{{else}}
<div class="two wide column">
<a class="ui button mini" disabled='true'>{{if .IsPrivate}} {{$.i18n.Tr "dataset.private"}} {{else}} {{$.i18n.Tr "dataset.public"}} {{end}}</a>
</div>
{{end}}
{{end}}
</div>

</div>
</div>
{{end}}


+ 1
- 1
templates/repo/datasets/index.tmpl View File

@@ -80,7 +80,7 @@
<div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
<h2>{{if eq .Type 0}}{{.i18n.Tr "repo.cloudbrain1"}}{{else}}{{.i18n.Tr "repo.cloudbrain2"}}{{end}}-{{.i18n.Tr "datasets"}}</h2>
<strong>{{if eq .Type 0}}{{.i18n.Tr "repo.cloudbrain1"}}{{else}}{{.i18n.Tr "repo.cloudbrain2"}}{{end}}-{{.i18n.Tr "datasets"}}</strong>
</div>
<div class="column right aligned" style="z-index:1">
<div class="ui right dropdown type jump item">


+ 61
- 7
templates/repo/home.tmpl View File

@@ -55,6 +55,40 @@
#contributorInfo > a.circular:nth-child(9n+8){
background-color: #bfd0aa;
}
.vue_menu {
cursor: auto;
position: absolute;
outline: none;
top: 100%;
margin: 0em;
padding: 0em 0em;
background: #fff;
font-size: 1em;
text-shadow: none;
text-align: left;
/* -webkit-box-shadow: 0px 2px 3px 0px rgb(34 36 38 / 15%); */
box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15);
border: 1px solid rgba(34,36,38,0.15);
border-radius: 0.28571429rem;
-webkit-transition: opacity 0.1s ease;
transition: opacity 0.1s ease;
z-index: 11;
will-change: transform, opacity;
width: 100% !important;
-webkit-animation-iteration-count: 1;
animation-iteration-count: 1;
-webkit-animation-duration: 300ms;
animation-duration: 300ms;
-webkit-animation-timing-function: ease;
animation-timing-function: ease;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}



</style>
<div class="repository file list">
{{template "repo/header" .}}
@@ -62,7 +96,7 @@
{{template "base/alert" .}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
<div class="ui repo-topic-edit grid form segment error" id="topic_edit" style="display:none">
<!-- <div class="ui repo-topic-edit grid form segment error" id="topic_edit" style="display:none">
<div class="fourteen wide column">
<div class="field">
<div class="ui fluid multiple search selection dropdown">
@@ -78,7 +112,8 @@
<a class="ui button primary" href="javascript:;" id="save_topic"
data-link="{{.RepoLink}}/topics">{{.i18n.Tr "repo.topic.done"}}</a>
</div>
</div>
</div> -->
{{end}}
<div class="hide" id="validate_prompt">
<span id="count_prompt">{{.i18n.Tr "repo.topic.count_prompt"}}</span>
@@ -232,11 +267,30 @@
{{end}}

<p class="ui" id="repo-topics">
<i class="grey bookmark icon"></i>
{{range .Topics}}<a class="ui repo-topic small label topic" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<a id="manage_topic">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}}
</p>
<div class="ui" id="repo-topics" style="display: flex;position: relative;margin-bottom: 1.0rem;">
<i class="grey bookmark icon"></i>
<div id="repo-topics1" style="flex: 1;">
<!-- {{if not .Topics}}
<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>
{{end}} -->
{{range .Topics}}
<a class="ui repo-topic small label topic" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>
{{end}}
</div>
<div>
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<i id="manage_topic" style="cursor: pointer;" class="plus icon"></i>{{end}}
</div>
<div id="topic_edit" class="vue_menu" style="display:none">
<div id="topic_edit1">
</div>
</div>
</div>
<p class="ui">


+ 1
- 1
templates/repo/modelarts/index.tmpl View File

@@ -262,7 +262,7 @@
{{.Status}}
</span>
<!-- 任务创建时间 -->
<span class="">{{TimeSinceUnix .CreatedUnix $.Lang}}</span>
<span class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>

<div class="seven wide column text right">


+ 468
- 0
web_src/js/components/EditTopics.vue View File

@@ -0,0 +1,468 @@
<template>
<div>
<div class="input-search">
<el-input v-model="input" clearable :autofocus="true" @input="changeValue" id="topics_input" @keyup.enter.native="postTopic" placeholder="搜索或创建标签">

</el-input>
<div class="scrolling-menu">
<div v-if="showSearchTopic" class="item-text" v-for="(arr,i) in array" @click="addTopics(i,arr)">
<div class="icon-wrapper">
<i style="line-height: 1.5;color: #303643;font-weight: 900;" v-if="showInitTopic[i]" class="el-icon-check" ></i>
</div>
<div class="text">{{arr.topic_name}} </div>
</div>
<div v-if="showInputValue" class="addition item-text" @click="postTopic">
点击或回车添加<b class="user-add-label-text">{{input}}</b>标签
</div>
<div v-if="showAddTopic" class="item-text" @click="addPostTopic">
<div class="icon-wrapper">
<i style="line-height: 1.5;color: #303643;font-weight: 900;" v-if="showAddFlage" class="el-icon-check" ></i>
</div>
<div class="text">{{input}}</div>
</div>

</div>

</div>
</div>
</template>

<script>

const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;

import $ from 'jquery'


export default {
data() {
return {
input:'',
params:{},
showInputValue:false,
showFlag:-1,
array:[],
showAddTopic:false,
showAddFlage:false,
showSearchTopic:true,
postUrl:'',
arrayTopics:[],
showInitTopic:[],

};
},
methods: {

addTopics(item,array){
if(!this.showInitTopic[item]){

this.arrayTopics.push(array.topic_name)
let topics = this.arrayTopics
let strTopics = topics.join(',')
let data = this.qs.stringify({
_csrf:csrf,
topics:strTopics
})
this.Post(data,topics)
this.$set(this.showInitTopic,item,true)
$('#repo-topics1').children('span').remove()

}else{
this.arrayTopics=this.arrayTopics.filter(ele=>{
return ele !== array.topic_name

})

let topics = this.arrayTopics
let strTopics = topics.join(',')
let data = this.qs.stringify({
_csrf:csrf,
topics:strTopics
})
this.Post(data,topics)
this.$set(this.showInitTopic,item,false)
if(this.arrayTopics.length===0){
console.log("set empty")
$('#repo-topics1').append('<span class="no-description text-italic">暂无标签</span>')
}else{
$('#repo-topics1').children('span').remove()
}

}
},
changeValue(){
if (this.input === ''){
this.array = this.arrayTopics
let data = []
this.showInitTopic = []
this.array.forEach((element,index) => {
let item = {}
item.topic_name = element
data.push(item)
this.showInitTopic.push(true)
});
this.array = data
this.showInputValue = false
this.showSearchTopic = true
}
else if(this.arrayTopics.indexOf(this.input)>-1){
this.showInputValue = false
this.showSearchTopic = false
}else{
this.showInitTopic = []
let timestamp=new Date().getTime()
this.params.q = this.input
this.params._ = timestamp
this.$axios.get('/api/v1/topics/search',{
params:this.params
}).then((res)=>{
this.array = res.data.topics

this.array.forEach((element,index) => {
if (this.arrayTopics.indexOf(element.topic_name)>-1){
this.showInitTopic.push(true)

}else{
this.showInitTopic.push(false)
}
});
})
this.showInputValue = true
this.showSearchTopic = true
}
this.showAddTopic = false

},
Post(data,topics){
this.$axios.post(this.postUrl,data).then(res=>{
const viewDiv = $('#repo-topics1');
viewDiv.children('.topic').remove();
if (topics.length) {
const topicArray = topics;
const last = viewDiv.children('a').last();
for (let i = 0; i < topicArray.length; i++) {
const link = $('<a class="ui repo-topic small label topic"></a>');
link.attr(
'href',
`${AppSubUrl}/explore/repos?q=${encodeURIComponent(
topicArray[i]
)}&topic=1`
);
link.text(topicArray[i]);
// link.insertBefore(last);
viewDiv.append(link)
}
}
viewDiv.show();
})
},
postTopic(){
const patter = /^[\u4e00-\u9fa5a-z0-9][\u4e00-\u9fa5a-zA-Z0-9-]{0,35}$/
let regexp = patter.test(this.input)
console.log("regexp",regexp)
if(!regexp){
this.$notify({
message: '主题必须以中文、字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符',
duration: 3000,
type:'error'
});
return
}else{
let topic = this.input
this.arrayTopics.push(topic)
let topics = this.arrayTopics
let strTopics = topics.join(',')
let data = this.qs.stringify({
_csrf:csrf,
topics:strTopics
})
this.Post(data,topics)
$('#repo-topics1').children('span').remove()
this.showInputValue = false
this.showAddTopic = true
this.showAddFlage = true
}

},
addPostTopic(){
if(this.showAddFlage){
this.arrayTopics.pop()
let topics = this.arrayTopics
let strTopics = topics.join(',')
let data = this.qs.stringify({
_csrf:csrf,
topics:strTopics
})
this.Post(data,topics)
if(this.arrayTopics.length===0){
console.log("add postTopic")
$('#repo-topics1').append('<span class="no-description text-italic">暂无标签</span>')
}else{
$('#repo-topics1').children('span').remove()
}
}
else if(!this.showAddFlage){
let topic = this.input
this.arrayTopics.push(topic)
let topics = this.arrayTopics
let strTopics = topics.join(',')
let data = this.qs.stringify({
_csrf:csrf,
topics:strTopics
})
this.Post(data,topics)
$('#repo-topics1').children('span').remove()
}
this.showAddFlage = !this.showAddFlage
},
initTopics(){
const mgrBtn = $('#manage_topic');
const editDiv = $('#topic_edit');
mgrBtn.on('click', (e) => {
// viewDiv.hide();
editDiv.css('display', ''); // show Semantic UI Grid
this.input = ''
if (this.input === ''){
this.array = this.arrayTopics
let data = []
this.showInitTopic = []
this.array.forEach((element,index) => {
let item = {}
item.topic_name = element
data.push(item)
this.showInitTopic.push(true)
});
this.array = data
this.showInputValue = false
this.showSearchTopic = true
}
stopPropagation(e);
});
$(document).bind('click',function(){
editDiv.css('display','none');

})
editDiv.click(function(e){
stopPropagation(e);
})


function stopPropagation(e) {
var ev = e || window.event;
if (ev.stopPropagation) {
ev.stopPropagation();
}
else if (window.event) {
window.event.cancelBubble = true;//兼容IE
}
}
}
},
computed:{
},
watch: {

input(newValue){
if (newValue === ''){
this.array = this.arrayTopics
let data = []
this.showInitTopic = []
this.array.forEach((element,index) => {
let item = {}
item.topic_name = element
data.push(item)
this.showInitTopic.push(true)
});
this.array = data
this.showInputValue = false
this.showSearchTopic = true
}
}
},
mounted() {
const context = this
this.postUrl = `${window.location.pathname}/topics`;
$('#repo-topics1').children('a').each(function(){
context.arrayTopics.push($(this).text())
});
if(this.arrayTopics.length===0){
$('#repo-topics1').append('<span class="no-description text-italic">暂无标签</span>')
}
this.changeValue()
} ,
created(){
this.initTopics();
this.input=''
}
};
</script>

<style scoped>
.input-search {
width: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
min-width: 10rem;
white-space: nowrap;
font-size: 1rem;
position: relative;
display: inline-block;
color: rgba(0,0,0,0.8);
padding: 8px;
}
/deep/ .el-input__inner{
border-color: #409eff;
}
.scrolling-menu{
border-top: none !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
display: block;
position: static;
overflow-y: auto;
border: none;
-webkit-box-shadow: none !important;
box-shadow: none !important;
border-radius: 0 !important;
margin: 0 !important;
min-width: 100% !important;
width: auto !important;
border-top: 1px solid rgba(34,36,38,0.15);
}
.item-text{
border-top: none;
padding-right: calc(1.14285714rem + 17px ) !important;
line-height: 1.333;
padding-top: 0.7142857rem !important;
padding-bottom: 0.7142857rem !important;
position: relative;
cursor: pointer;
display: block;
border: none;
height: auto;
text-align: left;
border-top: none;
line-height: 1em;
color: rgba(0,0,0,0.87);
padding: 0.78571429rem 1.14285714rem !important;
font-size: 1rem;
text-transform: none;
font-weight: normal;
-webkit-box-shadow: none;
box-shadow: none;
-webkit-touch-callout: none;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: -webkit-box !important;
display: -ms-flexbox !important;
display: flex !important;
}
.icon-wrapper{
text-align: left;
width: 24px;
height: 20px;
-ms-flex-negative: 0;
flex-shrink: 0;
}
.text{
max-width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
font-weight: 400;
color: #40485b;
}
.addition{
background: #f6f6f6;
}
.user-add-label-text{
max-width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0 4px;
}
</style>

+ 4
- 5
web_src/js/components/MinioUploader.vue View File

@@ -1,17 +1,16 @@
<template>
<div class="dropzone-wrapper dataset-files">
<div class="ui pointing below red basic label">
<i class="icon info circle"></i>只有zip格式的数据集才能发起云脑任务
</div>
<div
id="dataset"
class="dropzone"
/>
<p class="upload-info">
{{ file_status_text }}
<span class="success">{{ status }}</span>
<strong class="success text red">{{ status }}</strong>
</p>
<p>云脑1提供 <span class="text blue">CPU / GPU</span> 资源,云脑2提供 <span class="text blue">Ascend NPU</span> 资源;调试使用的数据集也需要上传到对应的环境。</p>
<p>说明:<br>
- 只有zip格式的数据集才能发起云脑任务;<br>
- 云脑1提供 <span class="text blue">CPU / GPU</span> 资源,云脑2提供 <span class="text blue">Ascend NPU</span> 资源;调试使用的数据集也需要上传到对应的环境。</p>
</div>
</template>



+ 5
- 2
web_src/js/components/ObsUploader.vue View File

@@ -6,9 +6,12 @@
/>
<p class="upload-info">
{{ file_status_text }}
<span class="success">{{ status }}</span>
<strong class="success text red">{{ status }}</strong>
</p>
<p>说明:<br>
- 只有zip格式的数据集才能发起云脑任务;<br>
- 云脑1提供 <span class="text blue">CPU / GPU</span> 资源,云脑2提供 <span class="text blue">Ascend NPU</span> 资源;调试使用的数据集也需要上传到对应的环境。
</p>
<p>云脑1提供 <span class="text blue">CPU / GPU</span> 资源,云脑2提供 <span class="text blue">Ascend NPU</span> 资源;调试使用的数据集也需要上传到对应的环境。</p>
</div>
</template>



+ 255
- 214
web_src/js/index.js View File

@@ -36,6 +36,7 @@ import MinioUploader from './components/MinioUploader.vue';
import ObsUploader from './components/ObsUploader.vue';
import EditAboutInfo from './components/EditAboutInfo.vue';
import Images from './components/Images.vue'
import EditTopics from './components/EditTopics.vue'

Vue.use(ElementUI);
Vue.prototype.$axios = axios;
@@ -2967,11 +2968,13 @@ $(document).ready(async () => {
initVueUploader();
initObsUploader();
initVueEditAbout();
initVueEditTopic();
initVueImages();
initTeamSettings();
initCtrlEnterSubmit();
initNavbarContentToggle();
initTopicbar();
// initTopicbar();
// closeTopicbar();
initU2FAuth();
initU2FRegister();
initIssueList();
@@ -3666,7 +3669,19 @@ function initVueEditAbout() {
});
}


function initVueEditTopic() {
const el = document.getElementById('topic_edit1');
if (!el) {
return;
}
new Vue({
el:'#topic_edit1',
render:h=>h(EditTopics)
})
}
function initVueImages() {
const el = document.getElementById('images');
console.log("el",el)
@@ -3677,6 +3692,7 @@ function initVueImages() {

new Vue({
el: '#images',
render: h => h(Images)
});
}
@@ -3932,218 +3948,243 @@ function initNavbarContentToggle() {
});
}

function initTopicbar() {
const mgrBtn = $('#manage_topic');
const editDiv = $('#topic_edit');
const viewDiv = $('#repo-topics');
const saveBtn = $('#save_topic');
const topicDropdown = $('#topic_edit .dropdown');
const topicForm = $('#topic_edit.ui.form');
const topicPrompts = getPrompts();

mgrBtn.on('click', () => {
viewDiv.hide();
editDiv.css('display', ''); // show Semantic UI Grid
});

function getPrompts() {
const hidePrompt = $('div.hide#validate_prompt');
const prompts = {
countPrompt: hidePrompt.children('#count_prompt').text(),
formatPrompt: hidePrompt.children('#format_prompt').text()
};
hidePrompt.remove();
return prompts;
}

saveBtn.on('click', () => {
const topics = $('input[name=topics]').val();

$.post(
saveBtn.data('link'),
{
_csrf: csrf,
topics
},
(_data, _textStatus, xhr) => {
if (xhr.responseJSON.status === 'ok') {
viewDiv.children('.topic').remove();
if (topics.length) {
const topicArray = topics.split(',');

const last = viewDiv.children('a').last();
for (let i = 0; i < topicArray.length; i++) {
const link = $('<a class="ui repo-topic small label topic"></a>');
link.attr(
'href',
`${AppSubUrl}/explore/repos?q=${encodeURIComponent(
topicArray[i]
)}&topic=1`
);
link.text(topicArray[i]);
link.insertBefore(last);
}
}
editDiv.css('display', 'none');
viewDiv.show();
}
}
)
.fail((xhr) => {
if (xhr.status === 422) {
if (xhr.responseJSON.invalidTopics.length > 0) {
topicPrompts.formatPrompt = xhr.responseJSON.message;

const {invalidTopics} = xhr.responseJSON;
const topicLables = topicDropdown.children('a.ui.label');

topics.split(',').forEach((value, index) => {
for (let i = 0; i < invalidTopics.length; i++) {
if (invalidTopics[i] === value) {
topicLables
.eq(index)
.removeClass('green')
.addClass('red');
}
}
});
} else {
topicPrompts.countPrompt = xhr.responseJSON.message;
}
}
})
.always(() => {
topicForm.form('validate form');
});
});

topicDropdown.dropdown({
allowAdditions: true,
forceSelection: false,
fields: {name: 'description', value: 'data-value'},
saveRemoteData: false,
label: {
transition: 'horizontal flip',
duration: 200,
variation: false,
blue: true,
basic: true
},
className: {
label: 'ui small label'
},
apiSettings: {
url: `${AppSubUrl}/api/v1/topics/search?q={query}`,
throttle: 500,
cache: false,
onResponse(res) {
const formattedResponse = {
success: false,
results: []
};
const stripTags = function (text) {
return text.replace(/<[^>]*>?/gm, '');
};

const query = stripTags(this.urlData.query.trim());
let found_query = false;
const current_topics = [];
topicDropdown
.find('div.label.visible.topic,a.label.visible')
.each((_, e) => {
current_topics.push(e.dataset.value);
});

if (res.topics) {
let found = false;
for (let i = 0; i < res.topics.length; i++) {
// skip currently added tags
if (current_topics.includes(res.topics[i].topic_name)) {
continue;
}

if (
res.topics[i].topic_name.toLowerCase() === query.toLowerCase()
) {
found_query = true;
}
formattedResponse.results.push({
description: res.topics[i].topic_name,
'data-value': res.topics[i].topic_name
});
found = true;
}
formattedResponse.success = found;
}

if (query.length > 0 && !found_query) {
formattedResponse.success = true;
formattedResponse.results.unshift({
description: query,
'data-value': query
});
} else if (query.length > 0 && found_query) {
formattedResponse.results.sort((a, b) => {
if (a.description.toLowerCase() === query.toLowerCase()) return -1;
if (b.description.toLowerCase() === query.toLowerCase()) return 1;
if (a.description > b.description) return -1;
if (a.description < b.description) return 1;
return 0;
});
}

return formattedResponse;
}
},
onLabelCreate(value) {
value = value.toLowerCase().trim();
this.attr('data-value', value)
.contents()
.first()
.replaceWith(value);
return $(this);
},
onAdd(addedValue, _addedText, $addedChoice) {
addedValue = addedValue.toLowerCase().trim();
$($addedChoice).attr('data-value', addedValue);
$($addedChoice).attr('data-text', addedValue);
}
});

$.fn.form.settings.rules.validateTopic = function (_values, regExp) {
const topics = topicDropdown.children('a.ui.label');
const status =
topics.length === 0 || (topics.last().attr('data-value').match(regExp) !== null && topics.last().attr('data-value').length <= 35);
if (!status) {
topics
.last()
.removeClass('green')
.addClass('red');
}
return status && topicDropdown.children('a.ui.label.red').length === 0;
};

topicForm.form({
on: 'change',
inline: true,
fields: {
topics: {
identifier: 'topics',
rules: [
{
type: 'validateTopic',
value: /^[\u4e00-\u9fa5a-z0-9][\u4e00-\u9fa5a-z0-9-]{0,105}$/,
prompt: topicPrompts.formatPrompt
},
{
type: 'maxCount[25]',
prompt: topicPrompts.countPrompt
}
]
}
}
});
}
// function initTopicbar() {
// const mgrBtn = $('#manage_topic');
// const editDiv = $('#topic_edit');
// const viewDiv = $('#repo-topics');
// const saveBtn = $('#save_topic');
// const topicDropdown = $('#topic_edit .dropdown');
// const topicForm = $('#topic_edit.ui.form');
// const topicInput = $("#topics_input")
// const topicPrompts = getPrompts();
// mgrBtn.on('click', (e) => {
// // viewDiv.hide();
// editDiv.css('display', ''); // show Semantic UI Grid
// topicInput.val('')
// console.log("-----------------asdasd",$("#topics_input"),$("#topics_input").val())
// stopPropagation(e);
// });
// $(document).bind('click',function(){
// editDiv.css('display','none');

// })
// editDiv.click(function(e){
// stopPropagation(e);
// })

// function getPrompts() {
// const hidePrompt = $('div.hide#validate_prompt');
// const prompts = {
// countPrompt: hidePrompt.children('#count_prompt').text(),
// formatPrompt: hidePrompt.children('#format_prompt').text()
// };
// hidePrompt.remove();
// return prompts;
// }

// function stopPropagation(e) {
// var ev = e || window.event;
// if (ev.stopPropagation) {
// ev.stopPropagation();
// }
// else if (window.event) {
// window.event.cancelBubble = true;//兼容IE
// }
// }


// saveBtn.on('click', () => {
// const topics = $('input[name=topics]').val();

// $.post(
// saveBtn.data('link'),
// {
// _csrf: csrf,
// topics
// },
// (_data, _textStatus, xhr) => {
// if (xhr.responseJSON.status === 'ok') {
// console.log("--------saveBtn------------")
// viewDiv.children('.topic').remove();
// if (topics.length) {
// const topicArray = topics.split(',');

// const last = viewDiv.children('a').last();
// for (let i = 0; i < topicArray.length; i++) {
// const link = $('<a class="ui repo-topic small label topic"></a>');
// link.attr(
// 'href',
// `${AppSubUrl}/explore/repos?q=${encodeURIComponent(
// topicArray[i]
// )}&topic=1`
// );
// link.text(topicArray[i]);
// link.insertBefore(last);
// }
// }
// editDiv.css('display', 'none');
// viewDiv.show();
// }
// }
// )
// .fail((xhr) => {
// if (xhr.status === 422) {
// if (xhr.responseJSON.invalidTopics.length > 0) {
// topicPrompts.formatPrompt = xhr.responseJSON.message;

// const {invalidTopics} = xhr.responseJSON;
// const topicLables = topicDropdown.children('a.ui.label');

// topics.split(',').forEach((value, index) => {
// for (let i = 0; i < invalidTopics.length; i++) {
// if (invalidTopics[i] === value) {
// topicLables
// .eq(index)
// .removeClass('green')
// .addClass('red');
// }
// }
// });
// } else {
// topicPrompts.countPrompt = xhr.responseJSON.message;
// }
// }
// })
// .always(() => {
// topicForm.form('validate form');
// });
// });

// topicDropdown.dropdown({
// allowAdditions: true,
// forceSelection: false,
// fields: {name: 'description', value: 'data-value'},
// saveRemoteData: false,
// label: {
// transition: 'horizontal flip',
// duration: 200,
// variation: false,
// blue: true,
// basic: true
// },
// className: {
// label: 'ui small label'
// },
// apiSettings: {
// url: `${AppSubUrl}/api/v1/topics/search?q={query}`,
// throttle: 500,
// cache: false,
// onResponse(res) {
// const formattedResponse = {
// success: false,
// results: []
// };
// const stripTags = function (text) {
// return text.replace(/<[^>]*>?/gm, '');
// };

// const query = stripTags(this.urlData.query.trim());
// let found_query = false;
// const current_topics = [];
// topicDropdown
// .find('div.label.visible.topic,a.label.visible')
// .each((_, e) => {
// current_topics.push(e.dataset.value);
// });

// if (res.topics) {
// let found = false;
// for (let i = 0; i < res.topics.length; i++) {
// // skip currently added tags
// if (current_topics.includes(res.topics[i].topic_name)) {
// continue;
// }

// if (
// res.topics[i].topic_name.toLowerCase() === query.toLowerCase()
// ) {
// found_query = true;
// }
// formattedResponse.results.push({
// description: res.topics[i].topic_name,
// 'data-value': res.topics[i].topic_name
// });
// found = true;
// }
// formattedResponse.success = found;
// }

// if (query.length > 0 && !found_query) {
// formattedResponse.success = true;
// formattedResponse.results.unshift({
// description: query,
// 'data-value': query
// });
// } else if (query.length > 0 && found_query) {
// formattedResponse.results.sort((a, b) => {
// if (a.description.toLowerCase() === query.toLowerCase()) return -1;
// if (b.description.toLowerCase() === query.toLowerCase()) return 1;
// if (a.description > b.description) return -1;
// if (a.description < b.description) return 1;
// return 0;
// });
// }

// return formattedResponse;
// }
// },
// onLabelCreate(value) {
// value = value.toLowerCase().trim();
// this.attr('data-value', value)
// .contents()
// .first()
// .replaceWith(value);
// return $(this);
// },
// onAdd(addedValue, _addedText, $addedChoice) {
// addedValue = addedValue.toLowerCase().trim();
// $($addedChoice).attr('data-value', addedValue);
// $($addedChoice).attr('data-text', addedValue);
// }
// });

// $.fn.form.settings.rules.validateTopic = function (_values, regExp) {
// const topics = topicDropdown.children('a.ui.label');
// const status =
// topics.length === 0 || (topics.last().attr('data-value').match(regExp) !== null && topics.last().attr('data-value').length <= 35);
// if (!status) {
// topics
// .last()
// .removeClass('green')
// .addClass('red');
// }
// return status && topicDropdown.children('a.ui.label.red').length === 0;
// };

// topicForm.form({
// on: 'change',
// inline: true,
// fields: {
// topics: {
// identifier: 'topics',
// rules: [
// {
// type: 'validateTopic',
// value: /^[\u4e00-\u9fa5a-z0-9][\u4e00-\u9fa5a-z0-9-]{0,105}$/,
// prompt: topicPrompts.formatPrompt
// },
// {
// type: 'maxCount[25]',
// prompt: topicPrompts.countPrompt
// }
// ]
// }
// }
// });
// }

window.toggleDeadlineForm = function () {
$('#deadlineForm').fadeToggle(150);


+ 15
- 2
web_src/less/_dataset.less View File

@@ -140,19 +140,32 @@
border: 1px solid #ffffff;
}
}
}
}
}

.item {
border-bottom: 1px solid rgba(34,36,38,.15);
.ui.buttons {
.button {
box-shadow: none !important;
}
}
}
.ui.grid > .row {
align-items: center;
}
.title {
font-size: 16px;
font-weight: bold;
margin: 0 6px;
margin: 0 6px;
overflow: hidden;
padding-right: 15px;
white-space: nowrap;
text-overflow: ellipsis;
display: block;
}
.directory-seperator {
padding: 0 4px;


+ 22
- 0
web_src/less/openi.less View File

@@ -220,3 +220,25 @@ footer .column{margin-bottom:0!important; padding-bottom:0!important;}
.ui.vertical.menu .dropdown.item .menu {
left: 50%;
}

// icon cloudbrain
.i-round{display:inline-block;width:18px;height:18px;background:url("/img/icons.svg");background-position: -496px -52px;}
.i-bg-organ{background-position: -496px -52px;}
.i-bg-stop{background-position: -459px -52px;}
.i-bg-running{background-position: -478px -52px;}
.i-bg-orange{background-position: -495px -51px;}
.i-bg-red{background-position: -532px -52px;}
.i-bg-green{background-position: -441px -52px;}
.i-bg-used{background-position: -514px -52px;}
.icon-bind{background-position: -550px -52px;}
.icon-unbind{background-position: -568px -52px;}
.showCircle{display:inline-block;background-image:url('/img/loading.gif');background-repeat:no-repeat;width:16px;height:16px;background-size:16px 16px;margin-right:5px;}
.text_over{
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
width: 100%;
}


Loading…
Cancel
Save