Browse Source

Merge pull request 'V20211018' (#560) from V20211018 into develop

Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/560
pull/562/head
lewis 3 years ago
parent
commit
da18807f9a
71 changed files with 4908 additions and 635 deletions
  1. +11
    -0
      custom/conf/app.ini.sample
  2. +1
    -0
      go.mod
  3. +9
    -0
      models/attachment.go
  4. +46
    -4
      models/cloudbrain.go
  5. +16
    -0
      models/issue_comment.go
  6. +71
    -23
      models/models.go
  7. +0
    -0
      models/release.go
  8. +6
    -0
      models/repo.go
  9. +43
    -0
      models/repo_activity_custom.go
  10. +14
    -0
      models/repo_list.go
  11. +60
    -0
      models/repo_statistic.go
  12. +5
    -4
      models/repo_watch.go
  13. +8
    -5
      models/star.go
  14. +12
    -0
      models/user.go
  15. +381
    -0
      models/user_business_analysis.go
  16. +6
    -3
      models/user_follow.go
  17. +25
    -0
      modules/cron/tasks_basic.go
  18. +9
    -0
      modules/git/repo_commit.go
  19. +369
    -0
      modules/git/repo_stats_custom.go
  20. +312
    -0
      modules/repository/elk_pagedata.go
  21. +73
    -65
      modules/setting/database.go
  22. +16
    -0
      modules/setting/setting.go
  23. +13
    -2
      options/locale/locale_en-US.ini
  24. +11
    -0
      options/locale/locale_zh-CN.ini
  25. +1653
    -0
      public/img/icons.svg
  26. BIN
      public/img/loading.gif
  27. +135
    -87
      public/self/labelTaskPage.js
  28. +4
    -3
      routers/home.go
  29. +2
    -1
      routers/private/internal.go
  30. +8
    -1
      routers/private/tool.go
  31. +78
    -1
      routers/repo/cloudbrain.go
  32. +2
    -0
      routers/repo/modelarts.go
  33. +122
    -0
      routers/repo/repo_statistic.go
  34. +1
    -0
      routers/repo/setting.go
  35. +59
    -0
      routers/repo/user_data_analysis.go
  36. +40
    -0
      routers/repo/view.go
  37. +15
    -0
      routers/repo/wiki.go
  38. +9
    -0
      routers/secure/user.go
  39. +3
    -0
      routers/user/auth.go
  40. +1
    -1
      templates/base/head_navbar.tmpl
  41. +1
    -1
      templates/base/head_navbar_home.tmpl
  42. +2
    -2
      templates/explore/dataset_search.tmpl
  43. +11
    -11
      templates/explore/repo_left.tmpl
  44. +14
    -14
      templates/explore/repo_list.tmpl
  45. +1
    -1
      templates/explore/repo_search.tmpl
  46. +2
    -2
      templates/explore/search.tmpl
  47. +1
    -1
      templates/org/repo_list.tmpl
  48. +70
    -15
      templates/repo/cloudbrain/index.tmpl
  49. +35
    -46
      templates/repo/datasets/dataset_list.tmpl
  50. +3
    -3
      templates/repo/datasets/index.tmpl
  51. +53
    -41
      templates/repo/datasets/label/index.tmpl
  52. +95
    -26
      templates/repo/home.tmpl
  53. +18
    -8
      templates/repo/issue/labels.tmpl
  54. +2
    -2
      templates/repo/issue/list.tmpl
  55. +8
    -2
      templates/repo/issue/milestone_issues.tmpl
  56. +22
    -6
      templates/repo/issue/milestone_new.tmpl
  57. +19
    -8
      templates/repo/issue/milestones.tmpl
  58. +1
    -1
      templates/repo/issue/navbar.tmpl
  59. +15
    -3
      templates/repo/issue/new.tmpl
  60. +1
    -1
      templates/repo/issue/search.tmpl
  61. +15
    -2
      templates/repo/issue/view.tmpl
  62. +47
    -13
      templates/repo/modelarts/index.tmpl
  63. +15
    -1
      templates/repo/pulls/commits.tmpl
  64. +15
    -1
      templates/repo/pulls/files.tmpl
  65. +1
    -1
      templates/user/dashboard/issues.tmpl
  66. +491
    -0
      web_src/js/components/EditTopics.vue
  67. +4
    -5
      web_src/js/components/MinioUploader.vue
  68. +5
    -2
      web_src/js/components/ObsUploader.vue
  69. +255
    -214
      web_src/js/index.js
  70. +15
    -2
      web_src/less/_dataset.less
  71. +22
    -0
      web_src/less/openi.less

+ 11
- 0
custom/conf/app.ini.sample View File

@@ -385,6 +385,17 @@ CONN_MAX_LIFETIME = 3s
; Database maximum number of open connections, default is 0 meaning no maximum
MAX_OPEN_CONNS = 0

[database_statistic]
DB_TYPE = postgres
HOST = 127.0.0.1:5432
NAME = statistic
USER =
PASSWD =
SCHEMA =
SSL_MODE = disable
CHARSET = utf8
PATH =

[indexer]
; Issue indexer type, currently support: bleve, db or elasticsearch, default is bleve
ISSUE_INDEXER_TYPE = bleve


+ 1
- 0
go.mod View File

@@ -52,6 +52,7 @@ require (
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
github.com/golang/protobuf v1.4.1 // indirect
github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/go-github/v24 v24.0.1
github.com/gorilla/context v1.1.1
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect


+ 9
- 0
models/attachment.go View File

@@ -464,3 +464,12 @@ func CanDelAttachment(isSigned bool, user *User, attach *Attachment) bool {
}
return false
}

func GetAttachmentSizeByDatasetID(datasetID int64) (int64, error) {
total, err := x.Where("dataset_id = ?", datasetID).SumInt(&Attachment{}, "size")
if err != nil {
return 0, err
}

return total, nil
}

+ 46
- 4
models/cloudbrain.go View File

@@ -5,11 +5,12 @@ import (
"fmt"
"strings"
"time"
"xorm.io/builder"
"xorm.io/xorm"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)

type CloudbrainStatus string
@@ -59,12 +60,18 @@ type Cloudbrain struct {
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
DeletedAt time.Time `xorm:"deleted"`
CanDebug bool `xorm:"-"`
CanDel bool `xorm:"-"`
Type int `xorm:"INDEX DEFAULT 0"`

User *User `xorm:"-"`
Repo *Repository `xorm:"-"`
}

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

type CloudBrainLoginResult struct {
Code string
Msg string
@@ -523,7 +530,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 +590,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()
@@ -620,6 +629,18 @@ func GetCloudbrainByJobID(jobID string) (*Cloudbrain, error) {
return getRepoCloudBrain(cb)
}

func GetCloudbrainsNeededStopByUserID(userID int64) ([]*Cloudbrain, error) {
cloudBrains := make([]*Cloudbrain, 0)
err := x.Cols("job_id", "status", "type").Where("user_id=? AND status !=?", userID, string(JobStopped)).Find(&cloudBrains)
return cloudBrains, err
}

func GetCloudbrainsNeededStopByRepoID(repoID int64) ([]*Cloudbrain, error) {
cloudBrains := make([]*Cloudbrain, 0)
err := x.Cols("job_id", "status", "type").Where("repo_id=? AND status !=?", repoID, string(JobStopped)).Find(&cloudBrains)
return cloudBrains, err
}

func SetCloudbrainStatusByJobID(jobID string, status CloudbrainStatus) (err error) {
cb := &Cloudbrain{JobID: jobID, Status: string(status)}
_, err = x.Cols("status").Where("cloudbrain.job_id=?", jobID).Update(cb)
@@ -650,3 +671,24 @@ func GetCloudbrainByName(jobName string) (*Cloudbrain, error) {
cb := &Cloudbrain{JobName: jobName}
return getRepoCloudBrain(cb)
}

func CanDelJob(isSigned bool, user *User, job *CloudbrainInfo) bool {
if !isSigned || job.Status != string(JobStopped) {
return false
}
repo, err := GetRepositoryByID(job.RepoID)
if err != nil {
log.Error("GetRepositoryByID failed:%v", err.Error())
return false
}
permission, _ := GetUserRepoPermission(repo, user)
if err != nil {
log.Error("GetUserRepoPermission failed:%v", err.Error())
return false
}

if (user.ID == job.UserID && permission.AccessMode >= AccessModeWrite) || user.IsAdmin || permission.AccessMode >= AccessModeAdmin {
return true
}
return false
}

+ 16
- 0
models/issue_comment.go View File

@@ -1016,3 +1016,19 @@ func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID
})
return err
}

func GetCommentCountByRepoID(repoID int64) (int64, error) {
//sql := fmt.Sprintf("select count(1) from comment where issue_id in (select id from issue where repo_id = %d) and type = %d;", repoID, CommentTypeComment)
//res, err := x.Query(sql)
//if err != nil {
// return 0, err
//}
//return int64(binary.BigEndian.Uint64(res[0]["count"])), nil

total, err := x.Where("issue_id in (select id from issue where repo_id = ?) and type = ?", repoID, CommentTypeComment).Count(&Comment{})
if err != nil {
return 0, err
}

return total, nil
}

+ 71
- 23
models/models.go View File

@@ -59,6 +59,9 @@ var (
x *xorm.Engine
tables []interface{}

xStatistic *xorm.Engine
tablesStatistic []interface{}

// HasEngine specifies if we have a xorm.Engine
HasEngine bool
)
@@ -132,14 +135,19 @@ func init() {
new(RecommendOrg),
)

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

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

func getEngine() (*xorm.Engine, error) {
connStr, err := setting.DBConnStr()
func getEngine(database *setting.DBInfo) (*xorm.Engine, error) {
connStr, err := setting.DBConnStr(database)
if err != nil {
return nil, err
}
@@ -153,14 +161,12 @@ func getEngine() (*xorm.Engine, error) {
}
engine.SetSchema(setting.Database.Schema)

HasEngine = true

return engine, nil
}

// NewTestEngine sets a new test xorm.Engine
func NewTestEngine(x *xorm.Engine) (err error) {
x, err = getEngine()
x, err = getEngine(setting.Database)
if err != nil {
return fmt.Errorf("Connect to database: %v", err)
}
@@ -171,43 +177,80 @@ func NewTestEngine(x *xorm.Engine) (err error) {
return x.StoreEngine("InnoDB").Sync2(tables...)
}

// SetEngine sets the xorm.Engine
// setEngine sets the xorm.Engine
func setEngine(engine *xorm.Engine, table []interface{}, database *setting.DBInfo) (err error) {
engine.SetMapper(names.GonicMapper{})
// WARNING: for serv command, MUST remove the output to os.stdout,
// so use log file to instead print to stdout.
engine.SetLogger(NewXORMLogger(setting.Database.LogSQL))
engine.ShowSQL(setting.Database.LogSQL)
engine.SetMaxOpenConns(setting.Database.MaxOpenConns)
engine.SetMaxIdleConns(setting.Database.MaxIdleConns)
engine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
engine.Sync2(table...)
MigrateCustom(engine)
return nil
}

func SetEngine() (err error) {
x, err = getEngine()
x, err = getEngine(setting.Database)
if err != nil {
return fmt.Errorf("Failed to connect to database: %v", err)
}
if err = setEngine(x, tables, setting.Database); err != nil {
return err
}

xStatistic, err = getEngine(setting.DatabaseStatistic)
if err != nil {
return fmt.Errorf("Failed to connect to database: %v", err)
}
if err = setEngine(xStatistic, tablesStatistic, setting.DatabaseStatistic); err != nil {
return err
}

x.SetMapper(names.GonicMapper{})
// WARNING: for serv command, MUST remove the output to os.stdout,
// so use log file to instead print to stdout.
x.SetLogger(NewXORMLogger(setting.Database.LogSQL))
x.ShowSQL(setting.Database.LogSQL)
x.SetMaxOpenConns(setting.Database.MaxOpenConns)
x.SetMaxIdleConns(setting.Database.MaxIdleConns)
x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
x.Sync2(tables...)
MigrateCustom(x)
return nil
}

// NewEngine initializes a new xorm.Engine
func NewEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
if err = SetEngine(); err != nil {
x, err = getEngine(setting.Database)
if err != nil {
return fmt.Errorf("Failed to connect to database: %v", err)
}
if err = newEngine(ctx, migrateFunc, x, tables, setting.Database); err != nil {
return fmt.Errorf("newEngine failed: %v", err)
}

xStatistic, err = getEngine(setting.DatabaseStatistic)
if err != nil {
return fmt.Errorf("Failed to connect to database: %v", err)
}
if err = newEngine(ctx, migrateFunc, xStatistic, tablesStatistic, setting.DatabaseStatistic); err != nil {
return fmt.Errorf("newEngine statistic failed: %v", err)
}

HasEngine = true

return nil
}

// newEngine initializes a new xorm.Engine
func newEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error, engine *xorm.Engine, table []interface{}, database *setting.DBInfo) (err error) {
if err = setEngine(engine, table, database); err != nil {
return err
}

x.SetDefaultContext(ctx)
engine.SetDefaultContext(ctx)

if err = x.Ping(); err != nil {
if err = engine.Ping(); err != nil {
return err
}

if err = migrateFunc(x); err != nil {
if err = migrateFunc(engine); err != nil {
return fmt.Errorf("migrate: %v", err)
}

if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
if err = engine.StoreEngine("InnoDB").Sync2(table...); err != nil {
return fmt.Errorf("sync database struct error: %v", err)
}

@@ -257,6 +300,11 @@ func Ping() error {
if x != nil {
return x.Ping()
}

if xStatistic != nil {
return xStatistic.Ping()
}

return errors.New("database not configured")
}



+ 0
- 0
models/release.go View File


+ 6
- 0
models/repo.go View File

@@ -1424,6 +1424,12 @@ func GetAllRepositories() ([]*Repository, error) {
return getALLRepositories(x)
}

func GetAllRepositoriesByFilterCols(columns ...string) ([]*Repository, error) {
repos := make([]*Repository, 0, 1000)
return repos, x.Cols(columns...).Find(&repos)

}

func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) {
repo.LowerName = strings.ToLower(repo.Name)



+ 43
- 0
models/repo_activity_custom.go View File

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

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

func GetRepoKPIStats(repo *Repository) (*git.RepoKPIStats, error) {
wikiPath := ""
if repo.HasWiki() {
wikiPath = repo.WikiPath()
}
return git.GetRepoKPIStats(repo.RepoPath(), wikiPath)
}

func GetAllUserKPIStats() (map[string]*git.UserKPIStats, error) {
authors := make(map[string]*git.UserKPIStats)
repositorys, err := GetAllRepositoriesByFilterCols("owner_name", "name")
if err != nil {
return nil, err
}

for _, repository := range repositorys {
authorsOneRepo, err1 := git.GetUserKPIStats(repository.RepoPath())
if err1 != nil {
return nil, err
}

for key, value := range authorsOneRepo {
if _, ok := authors[key]; !ok {
authors[key] = &git.UserKPIStats{

Name: value.Name,
Email: value.Email,
Commits: 0,
CommitLines: 0,
}
}
authors[key].Commits += value.Commits
authors[key].CommitLines += value.CommitLines

}

}
return authors, nil
}

+ 14
- 0
models/repo_list.go View File

@@ -166,6 +166,8 @@ type SearchRepoOptions struct {
Archived util.OptionalBool
// only search topic name
TopicOnly bool
//search by Specific TopicName
TopicName string
// include description in keyword search
IncludeDescription bool
// None -> include has milestones AND has no milestone
@@ -327,6 +329,18 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
}
cond = cond.And(keywordCond)
}
if opts.TopicName != "" {
var subQueryCond = builder.NewCond()
subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(opts.TopicName)})
subQuery := builder.Select("repo_topic.repo_id").From("repo_topic").
Join("INNER", "topic", "topic.id = repo_topic.topic_id").
Where(subQueryCond).
GroupBy("repo_topic.repo_id")

var topicNameCond = builder.In("id", subQuery)
cond = cond.And(topicNameCond)

}

if opts.Fork != util.OptionalBoolNone {
cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue})


+ 60
- 0
models/repo_statistic.go View File

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

import (
"code.gitea.io/gitea/modules/timeutil"
"fmt"
)

// RepoStatistic statistic info of all repository
type RepoStatistic struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"unique(s) NOT NULL"`
Date string `xorm:"unique(s) NOT NULL"`
NumWatches int64 `xorm:"NOT NULL DEFAULT 0"`
NumStars int64 `xorm:"NOT NULL DEFAULT 0"`
NumForks int64 `xorm:"NOT NULL DEFAULT 0"`
NumDownloads int64 `xorm:"NOT NULL DEFAULT 0"`
NumComments int64 `xorm:"NOT NULL DEFAULT 0"`
NumVisits int64 `xorm:"NOT NULL DEFAULT 0"`
NumClosedIssues int64 `xorm:"NOT NULL DEFAULT 0"`
NumVersions int64 `xorm:"NOT NULL DEFAULT 0"`
//develop months
NumDevMonths int64 `xorm:"NOT NULL DEFAULT 0"`
RepoSize int64 `xorm:"NOT NULL DEFAULT 0"`
DatasetSize int64 `xorm:"NOT NULL DEFAULT 0"`
NumModels int64 `xorm:"NOT NULL DEFAULT 0"`
NumWikiViews int64 `xorm:"NOT NULL DEFAULT 0"`
NumCommits int64 `xorm:"NOT NULL DEFAULT 0"`
NumIssues int64 `xorm:"NOT NULL DEFAULT 0"`
NumPulls int64 `xorm:"NOT NULL DEFAULT 0"`
IssueFixedRate float32 `xorm:"NOT NULL"`
NumContributor int64 `xorm:"NOT NULL DEFAULT 0"`
NumKeyContributor int64 `xorm:"NOT NULL DEFAULT 0"`

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}

func DeleteRepoStatDaily(date string) error {
sess := xStatistic.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return fmt.Errorf("Begin: %v", err)
}

if _, err := sess.Where("date = ?", date).Delete(&RepoStatistic{}); err != nil {
return fmt.Errorf("Delete: %v", err)
}

if err := sess.Commit(); err != nil {
sess.Close()
return fmt.Errorf("Commit: %v", err)
}

sess.Close()
return nil
}

func InsertRepoStat(repoStat *RepoStatistic) (int64, error) {
return xStatistic.Insert(repoStat)
}

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


+ 381
- 0
models/user_business_analysis.go View File

@@ -0,0 +1,381 @@
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"`

//repo
CreateRepoCount int `xorm:"NOT NULL DEFAULT 0"`

//login count, from elk
LoginCount int `xorm:"NOT NULL DEFAULT 0"`

//openi index
OpenIIndex int `xorm:"NOT NULL DEFAULT 0"`

//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)
CreateRepoCountMap := queryUserCreateRepo(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]
}

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

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 queryUserCreateRepo(start_unix int64, end_unix int64) map[int64]int {
sess := x.NewSession()
defer sess.Close()
sess.Select("id,owner_id,name").Table("repository").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
repoList := make([]*Repository, 0)
sess.Find(&repoList)
resultMap := make(map[int64]int)
log.Info("query Repository size=" + fmt.Sprint(len(repoList)))
for _, repoRecord := range repoList {
if _, ok := resultMap[repoRecord.OwnerID]; !ok {
resultMap[repoRecord.OwnerID] = 1
} else {
resultMap[repoRecord.OwnerID] += 1
}
}
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
}

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


+ 25
- 0
modules/cron/tasks_basic.go View File

@@ -163,6 +163,28 @@ func registerHandleBlockChainUnSuccessCommits() {
})
}

func registerHandleRepoStatistic() {
RegisterTaskFatal("handle_repo_statistic", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@daily",
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.RepoStatisticAuto()
return nil
})
}

func registerHandleUserStatistic() {
RegisterTaskFatal("handle_user_statistic", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@daily",
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.TimingCountData()
return nil
})
}

func initBasicTasks() {
registerUpdateMirrorTask()
registerRepoHealthCheck()
@@ -177,4 +199,7 @@ func initBasicTasks() {
registerHandleBlockChainUnSuccessRepos()
registerHandleBlockChainMergedPulls()
registerHandleBlockChainUnSuccessCommits()

registerHandleRepoStatistic()
registerHandleUserStatistic()
}

+ 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



+ 369
- 0
modules/git/repo_stats_custom.go View File

@@ -0,0 +1,369 @@
package git

import (
"bufio"
"bytes"
"fmt"
"net/url"
"sort"
"strconv"
"strings"
"time"

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

type RepoKPIStats struct {
Contributors int64
KeyContributors int64
DevelopAge int64
ContributorsAdded int64
CommitsAdded int64
CommitLinesModified int64
WikiPages int64
Authors []*UserKPITypeStats
}

type UserKPIStats struct {
Name string
Email string
Commits int64
CommitLines int64
}
type UserKPITypeStats struct {
UserKPIStats
isNewContributor bool //是否是4个月内的新增贡献者
}

func GetRepoKPIStats(repoPath string, wikiPath string) (*RepoKPIStats, error) {
stats := &RepoKPIStats{}

contributors, err := GetContributors(repoPath)
if err != nil {
return nil, err
}
timeUntil := time.Now()
fourMonthAgo := timeUntil.AddDate(0, -4, 0)
recentlyContributors, err := getContributors(repoPath, fourMonthAgo)
newContributersDict := make(map[string]struct{})
if err != nil {
return nil, err
}

if contributors != nil {
stats.Contributors = int64(len(contributors))
for _, contributor := range contributors {
if contributor.CommitCnt >= 3 {
stats.KeyContributors++
}

if recentlyContributors != nil {
for _, recentlyContributor := range recentlyContributors {
if recentlyContributor.Email == contributor.Email && recentlyContributor.CommitCnt == contributor.CommitCnt {
stats.ContributorsAdded++
newContributersDict[recentlyContributor.Email] = struct{}{}
}

}
}

}

}

err = setDevelopAge(repoPath, stats)
if err != nil {
return nil, fmt.Errorf("FillFromGit: %v", err)
}
err = setRepoKPIStats(repoPath, fourMonthAgo, stats, newContributersDict)

if err != nil {
return nil, fmt.Errorf("FillFromGit: %v", err)
}

setWikiPages(wikiPath, stats)
return stats, nil

}

func setDevelopAge(repoPath string, stats *RepoKPIStats) error {
args := []string{"log", "--no-merges", "--branches=*", "--format=%cd", "--date=short"}
stdout, err := NewCommand(args...).RunInDirBytes(repoPath)
if err != nil {
return err
}
scanner := bufio.NewScanner(bytes.NewReader(stdout))
scanner.Split(bufio.ScanLines)
developMonth := make(map[string]struct{})
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
month := l[0:strings.LastIndex(l, "-")]
if _, ok := developMonth[month]; !ok {
developMonth[month] = struct{}{}
}
}

stats.DevelopAge = int64(len(developMonth))
return nil
}

//获取一天内的用户贡献指标
func GetUserKPIStats(repoPath string) (map[string]*UserKPIStats, error) {
timeUntil := time.Now()
oneDayAgo := timeUntil.AddDate(0, 0, -1)
since := oneDayAgo.Format(time.RFC3339)
args := []string{"log", "--numstat", "--no-merges", "--branches=*", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}
stdout, err := NewCommand(args...).RunInDirBytes(repoPath)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(bytes.NewReader(stdout))
scanner.Split(bufio.ScanLines)
usersKPIStatses := make(map[string]*UserKPIStats)
var author string
p := 0
var email string
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Separator
case 2: // Commit sha-1
case 3: // Author
author = l
case 4: // E-mail
email = strings.ToLower(l)
if _, ok := usersKPIStatses[email]; !ok {
usersKPIStatses[email] = &UserKPIStats{
Name: author,
Email: email,
Commits: 0,
CommitLines: 0,
}
}

usersKPIStatses[email].Commits++
default: // Changed file
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
usersKPIStatses[email].CommitLines += c
}
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
usersKPIStatses[email].CommitLines += c
}
}

}
}
}

return usersKPIStatses, nil

}

func setRepoKPIStats(repoPath string, fromTime time.Time, stats *RepoKPIStats, newContributers map[string]struct{}) error {
since := fromTime.Format(time.RFC3339)
args := []string{"log", "--numstat", "--no-merges", "--branches=*", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}

stdout, err := NewCommand(args...).RunInDirBytes(repoPath)
if err != nil {
return err
}

scanner := bufio.NewScanner(bytes.NewReader(stdout))
scanner.Split(bufio.ScanLines)

authors := make(map[string]*UserKPITypeStats)

var author string
p := 0
var email string
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Separator
case 2: // Commit sha-1
stats.CommitsAdded++
case 3: // Author
author = l
case 4: // E-mail
email = strings.ToLower(l)
if _, ok := authors[email]; !ok {
authors[email] = &UserKPITypeStats{
UserKPIStats: UserKPIStats{
Name: author,
Email: email,
Commits: 0,
CommitLines: 0,
},
isNewContributor: false,
}
}
if _, ok := newContributers[email]; ok {
authors[email].isNewContributor = true
}

authors[email].Commits++
default: // Changed file
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
stats.CommitLinesModified += c
authors[email].CommitLines += c
}
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
stats.CommitLinesModified += c
authors[email].CommitLines += c
}
}

}
}
}

a := make([]*UserKPITypeStats, 0, len(authors))
for _, v := range authors {
a = append(a, v)
}
// Sort authors descending depending on commit count
sort.Slice(a, func(i, j int) bool {
return a[i].Commits > a[j].Commits
})

stats.Authors = a
return nil

}

func getContributors(repoPath string, fromTime time.Time) ([]Contributor, error) {
since := fromTime.Format(time.RFC3339)
cmd := NewCommand("shortlog", "-sne", "--all", fmt.Sprintf("--since='%s'", since))
stdout, err := cmd.RunInDir(repoPath)
if err != nil {
return nil, err
}
stdout = strings.Trim(stdout, "\n")
contributorRows := strings.Split(stdout, "\n")
if len(contributorRows) > 0 {
contributorsInfo := make([]Contributor, len(contributorRows))
for i := 0; i < len(contributorRows); i++ {
var oneCount string = strings.Trim(contributorRows[i], " ")
if strings.Index(oneCount, "\t") < 0 {
continue
}
number := oneCount[0:strings.Index(oneCount, "\t")]
commitCnt, _ := strconv.Atoi(number)
committer := oneCount[strings.Index(oneCount, "\t")+1 : strings.LastIndex(oneCount, " ")]
committer = strings.Trim(committer, " ")
email := oneCount[strings.Index(oneCount, "<")+1 : strings.Index(oneCount, ">")]
contributorsInfo[i] = Contributor{
commitCnt, committer, email,
}
}
return contributorsInfo, nil
}
return nil, nil
}

func setWikiPages(wikiPath string, stats *RepoKPIStats) {
wikiPages := 0

if wikiPath == "" {
stats.WikiPages = int64(wikiPages)
return
}

wikiRepo, commit, err := findWikiRepoCommit(wikiPath)
if err != nil {
if !IsErrNotExist(err) {
Log.Warn("GetBranchCommit", err)
}
stats.WikiPages = int64(wikiPages)
return
}

// Get page list.
entries, err := commit.ListEntries()
if err != nil {
if wikiRepo != nil {
wikiRepo.Close()
}
Log.Warn("GetBranchCommit", err)
stats.WikiPages = int64(wikiPages)
return

}

for _, entry := range entries {
if !entry.IsRegular() {
continue
}

wikiName, err := filenameToName(entry.Name())
if err != nil || wikiName == "_Sidebar" || wikiName == "_Footer" {
continue
}

wikiPages += 1

}
//确保wikiRepo用完被关闭
defer func() {
if wikiRepo != nil {
wikiRepo.Close()
}
}()
stats.WikiPages = int64(wikiPages)
return

}

func filenameToName(filename string) (string, error) {
if !strings.HasSuffix(filename, ".md") {
return "", fmt.Errorf("invalid file")
}
basename := filename[:len(filename)-3]
unescaped, err := url.QueryUnescape(basename)
if err != nil {
return "", err
}
return strings.Replace(unescaped, "-", " ", -1), nil
}

func findWikiRepoCommit(wikiPath string) (*Repository, *Commit, error) {
wikiRepo, err := OpenRepository(wikiPath)
if err != nil {

return nil, nil, err
}

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

+ 312
- 0
modules/repository/elk_pagedata.go View File

@@ -0,0 +1,312 @@
package repository

import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

"code.gitea.io/gitea/modules/setting"
)

//输入elk的json结构begin
type InputInfo struct {
Batch []Batch `json:"batch"`
}
type Fields struct {
Field string `json:"field"`
Format string `json:"format"`
}
type MatchPhrase struct {
Message string `json:"message"`
}
type Should struct {
MatchPhrase MatchPhrase `json:"match_phrase"`
}
type Bool struct {
Should []Should `json:"should"`
MinimumShouldMatch int `json:"minimum_should_match"`
}
type Timestamptest struct {
// Gte time.Time `json:"gte"`
Gte string `json:"gte"`
Lte string `json:"lte"`
Format string `json:"format"`
}
type Range struct {
Timestamptest Timestamptest `json:"@timestamptest"`
}

type FilterMatchPhrase struct {
UserName string `json:"userName.keyword,omitempty"`
ProjectName string `json:"projectName.keyword,omitempty"`
TagName string `json:"tagName.keyword,omitempty"`
}

type Filter struct {
Bool *Bool `json:"bool,omitempty"`
Range *Range `json:"range,omitempty"`
FilterMatchPhrase *FilterMatchPhrase `json:"match_phrase,omitempty"`
}
type MustNotMatchPhrase struct {
ProjectName string `json:"projectName"`
}
type MustNot struct {
MustNotMatchPhrase MustNotMatchPhrase `json:"match_phrase"`
}
type BoolIn struct {
Filter []Filter `json:"filter"`
MustNot []MustNot `json:"must_not"`
}
type Query struct {
BoolIn BoolIn `json:"bool"`
}
type Body struct {
Size int `json:"size"`
Fields []Fields `json:"fields"`
Query Query `json:"query"`
}
type Params struct {
Index string `json:"index"`
Body Body `json:"body"`
}
type Request struct {
Params Params `json:"params"`
}
type Batch struct {
Request Request `json:"request"`
}

//输入elk的json结构end

//elk输出的json结构begin
type Hits struct {
Total int `json:"total"`
}
type RawResponse struct {
Hits Hits `json:"hits"`
}
type Result struct {
RawResponse RawResponse `json:"rawResponse"`
Loaded int `json:"loaded"`
}
type ResultInfo struct {
Id int `json:"id"`
Result Result `json:"result"`
}

//elk输出的json结构end

//发送post请求到elk
func SendReqToElk(jsonStr []byte) (content string) {
ElkBase64Init := setting.ElkUser + ":" + setting.ElkPassword
ElkBase64 := base64.StdEncoding.EncodeToString([]byte(ElkBase64Init))
BasicElkBase64 := "Basic" + " " + ElkBase64
url := setting.ElkUrl
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("kbn-version", "7.13.2")
req.Header.Set("Authorization", BasicElkBase64)

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return string(body)
}

//处理返回的elk数据,只保留totalView,即访问量;loaded是分片载入次数,用来判断返回的数据是否准确
func GetResultFromElk(resultinfo ResultInfo, jobResult string) (loaded int, totalView int) {
var resultTest ResultInfo
errs := json.Unmarshal([]byte(jobResult), &resultTest)
fmt.Println(errs)
return resultTest.Result.Loaded, resultTest.Result.RawResponse.Hits.Total
}

//初始化传给elk的数据结构,给定用户名和项目名,查询的起止时间,返回初始化后的结构
func ProjectViewInit(User string, Project string, Gte string, Lte string) (projectViewInit InputInfo) {
var inputStruct InputInfo
inputStruct.Batch = make([]Batch, 1)
inputStruct.Batch[0].Request.Params.Index = setting.Index
inputStruct.Batch[0].Request.Params.Body.Size = 0
inputStruct.Batch[0].Request.Params.Body.Fields = make([]Fields, 1)
inputStruct.Batch[0].Request.Params.Body.Fields[0].Field = setting.TimeField
inputStruct.Batch[0].Request.Params.Body.Fields[0].Format = setting.ElkTimeFormat
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter = make([]Filter, 3)
//限定查询时间
var timeRange Range
timeRange.Timestamptest.Gte = Gte
timeRange.Timestamptest.Lte = Lte
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[0].Range = &timeRange
//限定用户
var userName FilterMatchPhrase
userName.UserName = User
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[1].FilterMatchPhrase = &userName
//限定项目
var projectName FilterMatchPhrase
projectName.ProjectName = Project
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[2].FilterMatchPhrase = &projectName
return inputStruct
}

//初始化传给elk的数据结构,给定查询信息和非项目名,查询的起止时间,返回初始化后的结构
func AllProjectViewInit(MessageInfo string, NotProject string, Gte string, Lte string) (allProjectViewInit InputInfo) {
var inputStruct InputInfo
inputStruct.Batch = make([]Batch, 1)
inputStruct.Batch[0].Request.Params.Index = setting.Index
inputStruct.Batch[0].Request.Params.Body.Size = 0
inputStruct.Batch[0].Request.Params.Body.Fields = make([]Fields, 1)
inputStruct.Batch[0].Request.Params.Body.Fields[0].Field = setting.TimeField
inputStruct.Batch[0].Request.Params.Body.Fields[0].Format = setting.ElkTimeFormat
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter = make([]Filter, 2)
//限定message
var bool Bool
bool.Should = make([]Should, 1)
bool.Should[0].MatchPhrase.Message = MessageInfo
bool.MinimumShouldMatch = 1
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[0].Bool = &bool
//限定查询时间
var timeRange Range
timeRange.Timestamptest.Gte = Gte
timeRange.Timestamptest.Lte = Lte
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[1].Range = &timeRange
//限定非项目
// var boolIn BoolIn
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.MustNot = make([]MustNot, 1)
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.MustNot[0].MustNotMatchPhrase.ProjectName = NotProject
return inputStruct
}

//初始化传给elk的数据结构,给定查询信息和tagName,查询的起止时间,返回初始化后的结构
func TagNameInit(MessageInfo string, Tagname string, Gte string, Lte string) (projectViewInit InputInfo) {
var inputStruct InputInfo
inputStruct.Batch = make([]Batch, 1)
inputStruct.Batch[0].Request.Params.Index = setting.Index
inputStruct.Batch[0].Request.Params.Body.Size = 0
inputStruct.Batch[0].Request.Params.Body.Fields = make([]Fields, 1)
inputStruct.Batch[0].Request.Params.Body.Fields[0].Field = setting.TimeField
inputStruct.Batch[0].Request.Params.Body.Fields[0].Format = setting.ElkTimeFormat
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter = make([]Filter, 3)
//限定message
var bool Bool
bool.Should = make([]Should, 1)
bool.Should[0].MatchPhrase.Message = MessageInfo
bool.MinimumShouldMatch = 1
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[0].Bool = &bool
//限定tagName
var tagName FilterMatchPhrase
tagName.TagName = Tagname
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[1].FilterMatchPhrase = &tagName
//限定查询时间
var timeRange Range
timeRange.Timestamptest.Gte = Gte
timeRange.Timestamptest.Lte = Lte
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[2].Range = &timeRange
return inputStruct
}

//向elk发送请求,将获取的结果只保留访问量,输入是初始化后的数据结构,返回访问量
func ViewInfo(viewInfo InputInfo) (totalView int) {
jsons, errs := json.Marshal(viewInfo)
if errs != nil {
fmt.Println("errs:", errs.Error())
}
// fmt.Println("viewInfoInit:",string(jsons))
var jsonStr = []byte(jsons)
var resultInfo ResultInfo
loaded, totalView := GetResultFromElk(resultInfo, SendReqToElk(jsonStr))
time := 0
for {
if loaded == 0 {
loaded_next, totalView := GetResultFromElk(resultInfo, SendReqToElk(jsonStr))
time++
if loaded_next != 0 && time < 100 {
fmt.Println("totalView:", totalView)
return totalView
}
if time > 100 {
break
}
} else {
break
}
}
fmt.Println("loaded:", loaded)
return totalView
}

// @title ProjectView
// @description 获取指定用户和项目的访问量
// @param User string "用户名"
// @param Project string "项目名"
// @param Gte string "起始时间" 如time.Now().AddDate(0, 0, -1).Format(time.RFC3339)
// @param Lte string "结束时间" 如time.Now().Format(time.RFC3339)
// @return totalView int "访问量"
func AppointProjectView(User string, Project string, Gte string, Lte string) (totalView int) {
InitInfo := ProjectViewInit(User, Project, Gte, Lte)
return ViewInfo(InitInfo)
}

//统计项目相关页面的访问量
type ProjectInfo struct {
/* 统计所有项目中该页面的浏览情况,不需要区分项目。以aiforge项目为例 */
//地址:https://git.openi.org.cn/OpenI/aiforge/datasets?type=0
Project_dataset_type_0 int
//地址:https://git.openi.org.cn/OpenI/aiforge/datasets?type=1
Project_dataset_type_1 int
//地址:https://git.openi.org.cn/OpenI/aiforge/issues
Project_issues int
//地址:https://git.openi.org.cn/OpenI/aiforge/labels
Project_labels int
//地址:https://git.openi.org.cn/OpenI/aiforge/milestones
Project_milestones int
//地址:https://git.openi.org.cn/OpenI/aiforge/pulls
Project_pulls int
//地址:https://git.openi.org.cn/OpenI/aiforge/release
Project_release int
//地址:https://git.openi.org.cn/OpenI/aiforge/wiki
Project_wiki int
//地址:https://git.openi.org.cn/OpenI/aiforge/activity
Project_activity int
//地址:https://git.openi.org.cn/OpenI/aiforge/cloudbrain
Project_cloudbrain int
//地址:https://git.openi.org.cn/OpenI/aiforge/modelarts
Project_modelarts int
//地址:https://git.openi.org.cn/OpenI/aiforge/blockchain
Project_blockchain int
//地址:https://git.openi.org.cn/OpenI/aiforge/watchers
Project_watchers int
//地址:https://git.openi.org.cn/OpenI/aiforge/stars
Project_stars int
//地址:https://git.openi.org.cn/OpenI/aiforge/forks
Project_forks int
}

// @title AllProjectView
// @description 获取指定用户和项目的访问量
// @param Gte string "起始时间" 如time.Now().AddDate(0, 0, -1).Format(time.RFC3339)
// @param Lte string "结束时间"
// @return projectInfo ProjectInfo "统计所有项目中页面的浏览情况,不需要区分项目"
func AllProjectView(Gte string, Lte string) (projectInfo ProjectInfo) {
projectInfo.Project_dataset_type_0 = ViewInfo(AllProjectViewInit("/datasets?type=0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_dataset_type_1 = ViewInfo(AllProjectViewInit("/datasets?type=1", "%{[request][2]}", Gte, Lte))
projectInfo.Project_issues = ViewInfo(AllProjectViewInit("/issues HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_labels = ViewInfo(TagNameInit("/labels HTTP/2.0", "labels", Gte, Lte))
projectInfo.Project_milestones = ViewInfo(AllProjectViewInit("/milestones HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_pulls = ViewInfo(AllProjectViewInit("/pulls HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_release = ViewInfo(AllProjectViewInit("/release HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_wiki = ViewInfo(AllProjectViewInit("/wiki HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_activity = ViewInfo(AllProjectViewInit("/activity HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_cloudbrain = ViewInfo(AllProjectViewInit("/cloudbrain HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_modelarts = ViewInfo(AllProjectViewInit("/modelarts HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_blockchain = ViewInfo(AllProjectViewInit("/blockchain HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_watchers = ViewInfo(AllProjectViewInit("/watchers HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_stars = ViewInfo(AllProjectViewInit("/stars HTTP/2.0", "%{[request][2]}", Gte, Lte))
projectInfo.Project_forks = ViewInfo(AllProjectViewInit("/forks HTTP/2.0", "%{[request][2]}", Gte, Lte))
return projectInfo
}

+ 73
- 65
modules/setting/database.go View File

@@ -24,111 +24,119 @@ var (
EnableSQLite3 bool

// Database holds the database settings
Database = struct {
Type string
Host string
Name string
User string
Passwd string
Schema string
SSLMode string
Path string
LogSQL bool
Charset string
Timeout int // seconds
UseSQLite3 bool
UseMySQL bool
UseMSSQL bool
UsePostgreSQL bool
DBConnectRetries int
DBConnectBackoff time.Duration
MaxIdleConns int
MaxOpenConns int
ConnMaxLifetime time.Duration
IterateBufferSize int
}{
Timeout: 500,
}
Database *DBInfo
DatabaseStatistic *DBInfo
)

type DBInfo struct {
Type string
Host string
Name string
User string
Passwd string
Schema string
SSLMode string
Path string
LogSQL bool
Charset string
Timeout int // seconds
UseSQLite3 bool
UseMySQL bool
UseMSSQL bool
UsePostgreSQL bool
DBConnectRetries int
DBConnectBackoff time.Duration
MaxIdleConns int
MaxOpenConns int
ConnMaxLifetime time.Duration
IterateBufferSize int
}

// GetDBTypeByName returns the dataase type as it defined on XORM according the given name
func GetDBTypeByName(name string) string {
return dbTypes[name]
}

// InitDBConfig loads the database settings
func InitDBConfig() {
sec := Cfg.Section("database")
Database.Type = sec.Key("DB_TYPE").String()
switch Database.Type {
// initDBConfig loads the database settings
func initDBConfig(section string, database *DBInfo) {
sec := Cfg.Section(section)
database.Type = sec.Key("DB_TYPE").String()
switch database.Type {
case "sqlite3":
Database.UseSQLite3 = true
database.UseSQLite3 = true
case "mysql":
Database.UseMySQL = true
database.UseMySQL = true
case "postgres":
Database.UsePostgreSQL = true
database.UsePostgreSQL = true
case "mssql":
Database.UseMSSQL = true
database.UseMSSQL = true
}
Database.Host = sec.Key("HOST").String()
Database.Name = sec.Key("NAME").String()
Database.User = sec.Key("USER").String()
if len(Database.Passwd) == 0 {
Database.Passwd = sec.Key("PASSWD").String()
database.Host = sec.Key("HOST").String()
database.Name = sec.Key("NAME").String()
database.User = sec.Key("USER").String()
if len(database.Passwd) == 0 {
database.Passwd = sec.Key("PASSWD").String()
}
Database.Schema = sec.Key("SCHEMA").String()
Database.SSLMode = sec.Key("SSL_MODE").MustString("disable")
Database.Charset = sec.Key("CHARSET").In("utf8", []string{"utf8", "utf8mb4"})
Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db"))
Database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500)
Database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(2)
if Database.UseMySQL {
Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFE_TIME").MustDuration(3 * time.Second)
database.Schema = sec.Key("SCHEMA").String()
database.SSLMode = sec.Key("SSL_MODE").MustString("disable")
database.Charset = sec.Key("CHARSET").In("utf8", []string{"utf8", "utf8mb4"})
database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db"))
database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500)
database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(2)
if database.UseMySQL {
database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFE_TIME").MustDuration(3 * time.Second)
} else {
Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFE_TIME").MustDuration(0)
database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFE_TIME").MustDuration(0)
}
Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0)
database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0)

Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50)
Database.LogSQL = sec.Key("LOG_SQL").MustBool(true)
Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50)
database.LogSQL = sec.Key("LOG_SQL").MustBool(true)
database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
}

func InitDBConfig() {
Database = new(DBInfo)
DatabaseStatistic = new(DBInfo)
initDBConfig("database", Database)
initDBConfig("database_statistic", DatabaseStatistic)
}

// DBConnStr returns database connection string
func DBConnStr() (string, error) {
func DBConnStr(database *DBInfo) (string, error) {
connStr := ""
var Param = "?"
if strings.Contains(Database.Name, Param) {
if strings.Contains(database.Name, Param) {
Param = "&"
}
switch Database.Type {
switch database.Type {
case "mysql":
connType := "tcp"
if Database.Host[0] == '/' { // looks like a unix socket
if database.Host[0] == '/' { // looks like a unix socket
connType = "unix"
}
tls := Database.SSLMode
tls := database.SSLMode
if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL
tls = "false"
}
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s",
Database.User, Database.Passwd, connType, Database.Host, Database.Name, Param, Database.Charset, tls)
database.User, database.Passwd, connType, database.Host, database.Name, Param, database.Charset, tls)
case "postgres":
connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Param, Database.SSLMode)
connStr = getPostgreSQLConnectionString(database.Host, database.User, database.Passwd, database.Name, Param, database.SSLMode)
case "mssql":
host, port := ParseMSSQLHostPort(Database.Host)
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd)
host, port := ParseMSSQLHostPort(database.Host)
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, database.Name, database.User, database.Passwd)
case "sqlite3":
if !EnableSQLite3 {
return "", errors.New("this binary version does not build support for SQLite3")
}
if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil {
if err := os.MkdirAll(path.Dir(database.Path), os.ModePerm); err != nil {
return "", fmt.Errorf("Failed to create directories: %v", err)
}
connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate", Database.Path, Database.Timeout)
connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate", database.Path, database.Timeout)
default:
return "", fmt.Errorf("Unknown database type: %s", Database.Type)
return "", fmt.Errorf("Unknown database type: %s", database.Type)
}

return connStr, nil


+ 16
- 0
modules/setting/setting.go View File

@@ -482,6 +482,14 @@ var (
PoolInfos string
Flavor string
FlavorInfos string

//elk config
ElkUrl string
ElkUser string
ElkPassword string
Index string
TimeField string
ElkTimeFormat string
)

// DateLang transforms standard language locale name to corresponding value in datetime plugin.
@@ -1201,6 +1209,14 @@ func NewContext() {
PoolInfos = sec.Key("POOL_INFOS").MustString("")
Flavor = sec.Key("FLAVOR").MustString("")
FlavorInfos = sec.Key("FLAVOR_INFOS").MustString("")

sec = Cfg.Section("elk")
ElkUrl = sec.Key("ELKURL").MustString("http://192.168.207.35:5601/internal/bsearch")
ElkUser = sec.Key("ELKUSER").MustString("Qizhi")
ElkPassword = sec.Key("ELKPASSWORD").MustString("Pcl2020")
Index = sec.Key("INDEX").MustString("filebeat-7.3.2*")
TimeField = sec.Key("TIMEFIELD").MustString(" @timestamptest")
ElkTimeFormat = sec.Key("ELKTIMEFORMAT").MustString("date_time")
}

func loadInternalToken(sec *ini.Section) string {


+ 13
- 2
options/locale/locale_en-US.ini View File

@@ -87,7 +87,7 @@ write = Write
preview = Preview
loading = Loading…

error404_index = Request forbidden by administrative rules
error404_index = Request forbidden by administrative rules
error500_index = Internal Server Error
error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it.
error500= Sorry, the site has encountered some problems, we are trying to <strong>fix the page</strong>, please try again later.
@@ -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,10 @@ 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
cloudbrain_task = Task Name
cloudbrain_operate = Operate
cloudbrain_status_createtime = Status/Createtime

template.items = Template Items
template.git_content = Git Content (Default Branch)
@@ -831,6 +835,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
@@ -845,6 +850,7 @@ filter_branch_and_tag = Filter branch or tag
branches = Branches
tags = Tags
issues = Issues
issues_detail = Detail
pulls = Pull Requests
labels = Labels
org_labels_desc = Organization level labels that can be used with <strong>all repositories</strong> under this organization
@@ -1241,6 +1247,11 @@ pulls.reject_count_1 = "%d change request"
pulls.reject_count_n = "%d change requests"
pulls.waiting_count_1 = "%d waiting review"
pulls.waiting_count_n = "%d waiting reviews"
pulls.commits_count_1=This branch is %d commit behind the upstream.
pulls.commits_count_n=This branch is %d commit behind the upstream.
pulls.fetch_upstream=Fetch upstream
pulls.upstream_up_to_date=No new commits to fetch
pulls.upstream_error=Cannot get upstream info

pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled.
pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually.


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

@@ -772,6 +772,10 @@ cloudbrain_selection=云脑选择
cloudbrain_platform_selection=选择您准备使用的云脑平台:
confirm_choice=确定
cloudbran1_tips=只有zip格式的数据集才能发起云脑任务
cloudbrain_creator=创建者
cloudbrain_task=任务名称
cloudbrain_operate=操作
cloudbrain_status_createtime=状态/创建时间

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

no_desc=暂无描述
no_label = 暂无标签
quick_guide=快速帮助
clone_this_repo=克隆当前项目
create_new_repo_command=从命令行创建一个新的项目
@@ -847,6 +852,7 @@ filter_branch_and_tag=过滤分支或标签
branches=分支列表
tags=标签列表
issues=任务
issues_detail=详情
pulls=合并请求
labels=标签
org_labels_desc=组织级别的标签,可以被本组织下的 <strong>所有项目</strong> 使用
@@ -1243,6 +1249,11 @@ pulls.reject_count_1=%d 变更请求
pulls.reject_count_n=%d 变更请求
pulls.waiting_count_1=%d 个正在等待审核
pulls.waiting_count_n=%d 个正在等待审核
pulls.commits_count_1=当前分支落后上游分支 %d 个提交
pulls.commits_count_n=当前分支落后上游分支 %d 个提交
pulls.fetch_upstream=拉取上游更新
pulls.upstream_up_to_date=上游分支没有新的更新
pulls.upstream_error=获取上游分支信息错误

pulls.no_merge_desc=由于未启用合并选项,此合并请求无法被合并。
pulls.no_merge_helper=在项目设置中启用合并选项或者手工合并请求。


+ 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

+ 135
- 87
public/self/labelTaskPage.js View File

@@ -23,7 +23,7 @@ var pageSize = 10;
var tableData;
var tablePageData;

var preDictTaskData;
var modelListData;
var dataSetTaskData;

var userInfoData;
@@ -35,8 +35,8 @@ console.log("repoId=" + repoId);

function setDataSetTask(){
dataset_task_list();
display_createdatasetlabel(0);
//dataset_task_list();
//display_createdatasetlabel(0);
//getUser();
//dislpayUser();
getLabelPropertyTask();
@@ -121,46 +121,31 @@ function countLabel(){
}

function display_createdatasetlabel(sindex=-1){
var html="";
for (var i=0;i<dataSetTaskData.length;i++){
if (i==sindex){
var row = "<option value=\""+dataSetTaskData[i].id+
"\" selected=\"\">"+dataSetTaskData[i].task_name+
"</option>";
$("#datasetlabeltaskname").attr({value:dataSetTaskData[i].task_name + "-人工标注"});
}else{
var row = "<option value=\""+dataSetTaskData[i].id+
"\">"+dataSetTaskData[i].task_name+
"</option>";
}
html=html+row;
}
console.log(html);
document.getElementById('dataset_list').innerHTML=html;
}



function setPredictTask(){
pre_predict_task_list();
display_createlabel(0);
getUser();
dislpayUser();
getLabelPropertyTask();
displayLabelPropertyTask();
//dataset_task_list();
//display_createdatasetlabel(1);


get_model_list();

displayModelTask();
$(".ui.predict.modal").modal("show");
}

function pre_predict_task_list(){
function get_model_list(){
$.ajax({
type:"GET",
url:ip + "/api/pre-predict-taskforLabel/",
url:ip + "/api/queryAlgModelForAutoLabel/",
headers: {
authorization:token,
},
dataType:"json",
async:false,
success:function(json){
preDictTaskData = json;
modelListData = json;
console.log(json);
// return json.token;
},
@@ -170,12 +155,26 @@ function pre_predict_task_list(){
});
}

function sele_Change(sele){
var predictTaskName = $('#pre_predict_task_for_label option:selected').text();
console.log("select predictTaskName =" + predictTaskName);
$("#labeltaskname").attr({value:predictTaskName+"-人工标注"});
function displayModelTask(){
var html="";
for (var i=0;i<modelListData.length;i++){
if (i==0){
var row = "<option value=\""+modelListData[i].id+
"\" selected=\"\">"+modelListData[i].model_name +
"</option>";
}else{
var row = "<option value=\""+modelListData[i].id+
"\">"+modelListData[i].model_name+
"</option>";
}
html=html+row;
}
console.log(html);

document.getElementById('model_list').innerHTML=html;
}


function sele_export_Change(sele){

var isNeedPicture = $('#isNeedPicture option:selected').val();
@@ -201,27 +200,13 @@ function dataset_sele_Change(sele){
$("#datasetlabeltaskname").attr({value:dataset_listName+"-人工标注"});
}



function display_createlabel(sindex=-1){
var html="";
for (var i=0;i<preDictTaskData.length;i++){
if (i==sindex){
var row = "<option value=\""+preDictTaskData[i].id+
"\" selected=\"\">"+preDictTaskData[i].task_name+
"</option>";
$("#labeltaskname").attr({value:preDictTaskData[i].task_name + "-人工标注"});
}else{
var row = "<option value=\""+preDictTaskData[i].id+
"\">"+preDictTaskData[i].task_name+
"</option>";
}
html=html+row;
}
console.log(html);
document.getElementById('pre_predict_task_for_label').innerHTML=html;
function dataset_auto_sele_Change(sele){
var dataset_listName = $('#dataset_list_auto option:selected').text();
console.log("select dataset_list_auto =" + dataset_listName);
$("#autolabeltaskname").attr({value:dataset_listName+"-自动标注"});
}


var createsucced;

function submit_datasettask(){
@@ -242,7 +227,7 @@ function submit_datasettask(){
}
var labelpropertytaskid = $('#labelpropertytask_dataset option:selected').val();
createsucced = true;
label_task_create(task_name, relate_task_id, 2,assign_user_id,labelpropertytaskid);
label_task_create(task_name, relate_task_id, 2,assign_user_id,labelpropertytaskid,-1);
if(createsucced){
$(".ui.dataset.modal").modal("hide");
//$("#labelDataModal").modal('hide');
@@ -250,25 +235,30 @@ function submit_datasettask(){
page(0,pageSize);
}

function submit_labeltask(){
console.log($('#labeltaskname').val());
var task_name = $('#labeltaskname').val();
function submit_autolabeltask(){
console.log($('#autolabeltaskname').val());
var task_name = $('#autolabeltaskname').val();
if (isEmpty(task_name) || task_name.length > 32){
alert("人工标注任务名称不能为空或者不能超过32个字符。");
alert("自动标注任务名称不能为空或者不能超过32个字符。");
return;
}
var relate_task_id = $('#pre_predict_task_for_label option:selected').val();
var model_id = $('#model_list option:selected').val();
if(isEmpty(model_id)){
alert("标注模型不能为空。");
return;
}
var relate_task_id = $('#dataset_list_auto option:selected').val();
if(isEmpty(relate_task_id)){
alert("关联的自动标注任务不能为空。");
alert("数据集对象不能为空。");
return;
}
var assign_user_id = $('#label_assign_user option:selected').val();
if(isEmpty(assign_user_id)){
assign_user_id = token;
}
var labelpropertytaskid = $('#labelpropertytask_dataset option:selected').val();
var labelpropertytaskid = $('#labelpropertytask_auto option:selected').val();
createsucced = true;
label_task_create(task_name, relate_task_id, 1,assign_user_id,labelpropertytaskid);
label_task_create(task_name, relate_task_id, 1,assign_user_id,labelpropertytaskid,model_id);
if(createsucced){
$("#labelModal").modal('hide');
}
@@ -276,10 +266,10 @@ function submit_labeltask(){
}


function label_task_create(task_name, relate_task_id, taskType,assign_user_id,labelpropertytaskid){
function label_task_create(task_name, relate_task_id, taskType,assign_user_id,labelpropertytaskid,model_id){
var task_flow_type = $('#task_flow_type option:selected').val();
var task_flow_type = $('#task_flow_type option:selected').val();
var relate_other_label_task = [];
if(task_flow_type == 2){
@@ -309,20 +299,21 @@ function label_task_create(task_name, relate_task_id, taskType,assign_user_id,la
'assign_user_id':assign_user_id,
'task_flow_type':task_flow_type,
'relate_task_id':relate_task_id,//task id
'relate_other_label_task': relate_other_label_task_jsonstr,
'relate_other_label_task': relate_other_label_task_jsonstr,
"taskType": taskType,
"appid": repoId,
"createUserName":userName,
"labelPropertyTaskId":labelpropertytaskid
"appid": repoId,
"createUserName":userName,
"labelPropertyTaskId":labelpropertytaskid,
"modelId":model_id
}),
success:function(res){
console.log(res);
if(res.code == 0){
alert("人工标注任务创建成功!");
alert("自动标注任务创建成功!");
createsucced = true;
}
else{
alert("创建人工标注任务失败," + res.message);
alert("创建自动标注任务失败," + res.message);
createsucced = false;
}
},
@@ -433,11 +424,11 @@ function delete_labeltask(){
return;
}
var Check = $("table[id='label_task_list'] input[type=checkbox]:checked");//在table中找input下类型为checkbox属性为选中状态的数据
      Check.each(function () {//遍历
            var row = $(this).parent("td").parent("tr");//获取选中行
            var id = row.find("[id='labeltask_id']").html();//获取name='Sid'的值
            delete_labeltask_byid(id);
        });
Check.each(function () {//遍历
var row = $(this).parent("td").parent("tr");//获取选中行
var id = row.find("[id='labeltask_id']").html();//获取name='Sid'的值
delete_labeltask_byid(id);
});
page(0,pageSize);
}

@@ -480,13 +471,13 @@ function delete_labeltask_byid(label_task_id){

function getTaskTypeDesc(task_type){
if(task_type == 1){
return "自动标注结果";
return "图片-自动标注";
}else if(task_type == 2){
return "原始数据集-图片";
return "图片";
}else if(task_type == 3){
return "原始数据集-CT影像";
return "CT影像";
}else if(task_type == 4){
return "原始数据集-视频";
return "视频";
}
return "其它";
}
@@ -508,6 +499,12 @@ function getTaskSataus(task_status,task_status_desc){
else if(task_status == -1){
return "关联的数据集已经被删除。"
}
else if(task_status == 20){
return "自动标注进行中:" + task_status_desc;
}else if(task_status == 21){
return task_status_desc;
}
return "";
}

function getVerify(task_status,id,task_type){
@@ -522,7 +519,7 @@ function getVerify(task_status,id,task_type){
}

function getLabel(task_status,id,task_type,task_flow_type){
if(task_status == 0 && (userType == 1 || userType == 0)){
if((task_status == 0 || task_status == 21) && (userType == 1 || userType == 0)){
return "<a onclick=\"personLabel(\'" + id + "\'," + task_type + ")\"><b>" + getLabelDesc(task_flow_type) + "标注</b></a><br>";
}else{
return "";
@@ -570,8 +567,59 @@ function display_list(){

$('#label_task_list tr').find('td:eq(1)').hide();
$('#label_task_list tr').find('th:eq(1)').hide();
}
isNeedToRefresh = false;
taskNeedIntervalToRefresh();
}

var isNeedToRefresh;
var refreshTimeId = [];
var refreshCount;
var refreshMaxTime;

function taskNeedIntervalToRefresh(){
var isNeedToSetInterval = false;
refreshMaxTime= 1;
if(!isEmpty(tableData)){
for (var i=0;i<tableData.length;i++){
if(tableData[i].task_status == 20){
console.log("有任务在自动标注中。需要自动刷新。");
isNeedToRefresh = true;//有任务在进行中才刷新,否则不刷新。
refreshMaxTime = 500;
break;
}
}
}
if(!isEmpty(refreshTimeId)){
for(var i =0; i < refreshTimeId.length ;i++){
console.log("清除定时器。refreshTimeId=" + refreshTimeId[i]);
window.clearInterval(refreshTimeId[i]);
}
refreshTimeId = [];
}
if(isNeedToRefresh){
refreshCount = 0;
var tmpTimeId = self.setInterval("clockRefresh('" + refreshMaxTime +"')",5000);//5秒刷新
refreshTimeId.push(tmpTimeId);
console.log("开始刷新。tmpTimeId=" + tmpTimeId);
}

}
function clockRefresh(refreshMaxTime){
refreshCount = refreshCount + 1;
if(refreshCount > refreshMaxTime){
for(var i =0; i < refreshTimeId.length ;i++){
console.log("超过刷新最大次数,清除定时器。timeId=" + refreshTimeId[i]);
window.clearInterval(refreshTimeId[i]);
}
refreshTimeId = [];
return;
}
var current = $('#displayPage1').text();
console.log("开始刷新。current=" + current);
if(current >= 1){
page(current - 1,pageSize);
}
}

function startToLabel(taskid, task_type){//从审核转回标注,标注人不变。
$.ajax({
@@ -692,12 +740,12 @@ function setMultiTaskId(){
return;
}
var taskList = [];
  Check.each(function () {//遍历
      var row = $(this).parent("td").parent("tr");//获取选中行
      var id = row.find("[id='labeltask_id']").html();//获取name='Sid'的值
taskList.push(id);
      //$('#hide_labeltaskid').val(id);
  });
Check.each(function () {//遍历
var row = $(this).parent("td").parent("tr");//获取选中行
var id = row.find("[id='labeltask_id']").html();//获取name='Sid'的值
taskList.push(id);
//$('#hide_labeltaskid').val(id);
});
setTaskId(JSON.stringify(taskList));
}


+ 4
- 3
routers/home.go View File

@@ -149,8 +149,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {

//todo:support other topics
keyword := strings.Trim(ctx.Query("q"), " ")
topicOnly := ctx.QueryBool("topic")
ctx.Data["TopicOnly"] = topicOnly
topic := strings.Trim(ctx.Query("topic"), " ")

repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
ListOptions: models.ListOptions{
@@ -164,7 +163,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
OwnerID: opts.OwnerID,
AllPublic: true,
AllLimited: true,
TopicOnly: topicOnly,
TopicName: topic,
IncludeDescription: setting.UI.SearchRepoDescription,
})
if err != nil {
@@ -177,6 +176,8 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
repo.Active = int64(repo.NumIssues) + int64(repo.NumPulls) + int64(repo.NumCommit)
}
ctx.Data["Keyword"] = keyword
ctx.Data["Topic"] = topic

ctx.Data["Total"] = count
ctx.Data["Repos"] = repos
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled


+ 2
- 1
routers/private/internal.go View File

@@ -42,7 +42,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/manager/shutdown", Shutdown)
m.Post("/manager/restart", Restart)
m.Post("/manager/flush-queues", bind(private.FlushOptions{}), FlushQueues)
m.Post("/cmd/update_all_repo_commit_cnt", UpdateAllRepoCommitCnt)
m.Post("/tool/update_all_repo_commit_cnt", UpdateAllRepoCommitCnt)
m.Post("/tool/repo_stat", RepoStatisticManually)

}, CheckInternalToken)
}

routers/private/cmd.go → routers/private/tool.go View File

@@ -5,11 +5,13 @@
package private

import (
"gitea.com/macaron/macaron"
"net/http"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/repo"

"gitea.com/macaron/macaron"
)

func UpdateAllRepoCommitCnt(ctx *macaron.Context) {
@@ -35,3 +37,8 @@ func UpdateAllRepoCommitCnt(ctx *macaron.Context) {
"error_msg": "",
})
}

func RepoStatisticManually(ctx *macaron.Context) {
date := ctx.Query("date")
repo.RepoStatisticDaily(date)
}

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

@@ -4,6 +4,7 @@ import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
@@ -13,6 +14,8 @@ import (
"strings"
"time"

"code.gitea.io/gitea/modules/modelarts"

"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/storage"

@@ -67,11 +70,13 @@ 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) {
if task.Status == string(models.JobRunning) && (timestamp-int64(task.Cloudbrain.CreatedUnix) > 10) {
ciTasks[i].CanDebug = true
} else {
ciTasks[i].CanDebug = false
}

ciTasks[i].CanDel = models.CanDelJob(ctx.IsSigned, ctx.User, task)
}

pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5)
@@ -361,6 +366,78 @@ func CloudBrainStop(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain")
}

func StopJobsByUserID(userID int64) {
cloudBrains, err := models.GetCloudbrainsNeededStopByUserID(userID)
if err != nil {
log.Warn("Failed to get cloudBrain info", err)
return
}
StopJobs(cloudBrains)

}

func StopJobsByRepoID(repoID int64) {
cloudBrains, err := models.GetCloudbrainsNeededStopByRepoID(repoID)
if err != nil {
log.Warn("Failed to get cloudBrain info", err)
return
}
StopJobs(cloudBrains)
}

/**

*/
func StopJobs(cloudBrains []*models.Cloudbrain) {

for _, taskInfo := range cloudBrains {

if taskInfo.Type == models.TypeCloudBrainOne {
err := retry(3, time.Second*30, func() error {
return cloudbrain.StopJob(taskInfo.JobID)
})

logErrorAndUpdateJobStatus(err, taskInfo)
} else {
param := models.NotebookAction{
Action: models.ActionStop,
}
err := retry(3, time.Second*30, func() error {
_, err := modelarts.StopJob(taskInfo.JobID, param)
return err
})
logErrorAndUpdateJobStatus(err, taskInfo)
}

}
}

func retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; i < attempts; i++ {
if i > 0 {
log.Warn("retrying after error:", err)
time.Sleep(sleep)
}
err = f()
if err == nil {
return nil
}
}
return fmt.Errorf("after %d attempts, last error: %s", attempts, err)
}

func logErrorAndUpdateJobStatus(err error, taskInfo *models.Cloudbrain) {
if err != nil {
log.Warn("Failed to stop cloudBrain job:"+taskInfo.JobID, err)
} else {
taskInfo.Status = string(models.JobStopped)
err = models.UpdateJob(taskInfo)
if err != nil {
log.Warn("UpdateJob failed", err)
}
}
}

func CloudBrainDel(ctx *context.Context) {
var jobID = ctx.Params(":jobid")
task, err := models.GetCloudbrainByJobID(jobID)


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

@@ -57,6 +57,8 @@ func ModelArtsIndex(ctx *context.Context) {
} else {
ciTasks[i].CanDebug = false
}

ciTasks[i].CanDel = models.CanDelJob(ctx.IsSigned, ctx.User, task)
}

pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5)


+ 122
- 0
routers/repo/repo_statistic.go View File

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

import (
"time"

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

//auto daily or manually
func RepoStatisticAuto() {
log.Info("", time.Now())
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
RepoStatisticDaily(yesterday)
}

func RepoStatisticDaily(date string) {
log.Info("%s", date)
if err := models.DeleteRepoStatDaily(date); err != nil {
log.Error("DeleteRepoStatDaily failed: %v", err.Error())
return
}

repos, err := models.GetAllRepositories()
if err != nil {
log.Error("GetAllRepositories failed: %v", err.Error())
return
}

for _, repo := range repos {
log.Info("start statistic: %s", repo.Name)
repoGitStat, err := models.GetRepoKPIStats(repo)
if err != nil {
log.Error("GetRepoKPIStats failed: %s", repo.Name)
log.Error("failed statistic: %s", repo.Name)
continue
}

var issueFixedRate float32
if repo.NumIssues != 0 {
issueFixedRate = float32(repo.NumClosedIssues) / float32(repo.NumIssues)
}

numVersions, err := models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{})
if err != nil {
log.Error("GetReleaseCountByRepoID failed: %s", repo.Name)
log.Error("failed statistic: %s", repo.Name)
continue
}

datasetSize, err := getDatasetSize(repo)
if err != nil {
log.Error("getDatasetSize failed: %s", repo.Name)
log.Error("failed statistic: %s", repo.Name)
continue
}

numComments, err := models.GetCommentCountByRepoID(repo.ID)
if err != nil {
log.Error("GetCommentCountByRepoID failed: %s", repo.Name)
log.Error("failed statistic: %s", repo.Name)
continue
}

//beginTime, endTime := getStatTime(date)
//numVisits := repository.AppointProjectView(repo.OwnerName, repo.Name, beginTime, endTime)
numVisits := 0

repoStat := models.RepoStatistic{
RepoID: repo.ID,
Date: date,
NumWatches: int64(repo.NumWatches),
NumStars: int64(repo.NumStars),
NumDownloads: repo.CloneCnt,
NumComments: numComments,
NumVisits: int64(numVisits),
NumClosedIssues: int64(repo.NumClosedIssues),
NumVersions: numVersions,
NumDevMonths: repoGitStat.DevelopAge,
RepoSize: repo.Size,
DatasetSize: datasetSize,
NumModels: 0,
NumWikiViews: repoGitStat.WikiPages,
NumCommits: repo.NumCommit,
NumIssues: int64(repo.NumIssues),
NumPulls: int64(repo.NumPulls),
IssueFixedRate: issueFixedRate,
NumContributor: repoGitStat.Contributors,
NumKeyContributor: repoGitStat.KeyContributors,
}

if _, err = models.InsertRepoStat(&repoStat); err != nil {
log.Error("InsertRepoStat failed: %s", repo.Name)
log.Error("failed statistic: %s", repo.Name)
continue
}

log.Info("finish statistic: %s", repo.Name)
}

}

func getDatasetSize(repo *models.Repository) (int64, error) {
dataset, err := models.GetDatasetByRepo(repo)
if err != nil {
return 0, err
}

return models.GetAttachmentSizeByDatasetID(dataset.ID)
}

func getStatTime(timeStr string) (string, string) {
t, _ := time.Parse("2006-01-02", timeStr)
timeNumber := t.Unix()
beginTimeNumber := timeNumber - 8*60*60
endTimeNumber := timeNumber + 16*60*60
beginTime := time.Unix(beginTimeNumber, 0).Format(time.RFC3339)
endTime := time.Unix(endTimeNumber, 0).Format(time.RFC3339)
log.Info("%s, %s", beginTime, endTime)

return beginTime, endTime
}

+ 1
- 0
routers/repo/setting.go View File

@@ -440,6 +440,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
return
}
log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
go StopJobsByRepoID(repo.ID)

ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
ctx.Redirect(ctx.Repo.Owner.DashboardLink())


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

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

import (
"time"

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

func TimingCountData() {
//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, err := models.GetAllRepositories()
if err != nil {
log.Error("query repo error.")
return
}
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)
}

+ 40
- 0
routers/repo/view.go View File

@@ -790,6 +790,46 @@ func renderCode(ctx *context.Context) {
}
}

//如果是fork的仓库
if ctx.Repo.Repository.IsFork {
//获得fetchUpstream对应的分支参数
/*
// 1. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headBranch}
// 2. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headOwner}:{:headBranch}
// 3. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headOwner}/{:headRepoName}:{:headBranch}
*/
baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath())
defer baseGitRepo.Close()
if err != nil {
log.Error("error open baseRepo:%s",ctx.Repo.Repository.BaseRepo.RepoPath())
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error
}else{
if _,error:= baseGitRepo.GetBranch(ctx.Repo.BranchName);error==nil{
//base repo has the same branch, then compare between current repo branch and base repo's branch
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName
ctx.SetParams("*",compareUrl)
ctx.Data["UpstreamSameBranchName"] = true
}else{
//else, compare between current repo branch and base repo's default branch
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.Repository.BaseRepo.DefaultBranch
ctx.SetParams("*",compareUrl)
ctx.Data["UpstreamSameBranchName"] = false
}
_, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx)
defer headGitRepo.Close()
if compareInfo!= nil {
if compareInfo.Commits!=nil {
log.Info("compareInfoCommits数量:%d",compareInfo.Commits.Len())
ctx.Data["FetchUpstreamCnt"] = compareInfo.Commits.Len()
}else{
log.Info("compareInfo nothing different")
ctx.Data["FetchUpstreamCnt"] = 0
}
}else{
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error
}
}
}
ctx.Data["Paths"] = paths
ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames


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


+ 9
- 0
routers/secure/user.go View File

@@ -7,6 +7,7 @@ package secure

import (
"net/http"
"net/mail"
"strings"

"code.gitea.io/gitea/models"
@@ -63,6 +64,14 @@ func CreateUser(ctx *context.Context, form api.CreateUserOption) {
// "422":
// "$ref": "#/responses/validationError"

_, err1 := mail.ParseAddress(form.Email)
if err1 != nil {
ctx.JSON(http.StatusBadRequest, map[string]string{
"error_msg": "Email format is wrong.",
})
return
}

u := &models.User{
Name: form.Username,
FullName: form.FullName,


+ 3
- 0
routers/user/auth.go View File

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

"code.gitea.io/gitea/routers/repo"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/oauth2"
@@ -1056,6 +1058,7 @@ func SignOut(ctx *context.Context) {
})
}
HandleSignOut(ctx)
go repo.StopJobsByUserID(ctx.User.ID)
ctx.Redirect(setting.AppSubURL + "/")
}



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


+ 2
- 2
templates/explore/dataset_search.tmpl View File

@@ -1,12 +1,12 @@
<div class="repos--seach">
<div class="ui container">
<div class="ui two column centered grid">
<form class="mobile ten wide tablet computer column ui form ignore-dirty">
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty">
<div class="ui fluid action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
<input type="hidden" name="tab" value="{{$.TabName}}">
<input type="hidden" name="sort" value="{{$.SortType}}">
<button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
<button class="ui green button">{{.i18n.Tr "explore.search"}}</button>
</div>
</form>
</div>


+ 11
- 11
templates/explore/repo_left.tmpl View File

@@ -6,67 +6,67 @@
</svg>
全部领域
</a>
<a class="{{if eq $.Keyword "大模型"}}active {{end}}item" href="/explore/repos?q=大模型&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "大模型"}}active {{end}}item" href="/explore/repos?q=&topic=大模型&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M19 3H5C3.89 3 3 3.89 3 5V19C3 20.11 3.9 21 5 21H19C20.11 21 21 20.11 21 19V5C21 3.89 20.1 3 19 3M16.1 15.9C15.07 15.9 14.09 15.5 13.35 14.76L12.71 14.12L14.13 12.71L14.76 13.34C15.12 13.7 15.6 13.9 16.11 13.9C17.15 13.9 18 13.05 18 12S17.15 10.1 16.1 10.1C15.6 10.1 15.12 10.3 14.76 10.66L10.65 14.76C9.91 15.5 8.94 15.9 7.9 15.9C5.75 15.9 4 14.15 4 12S5.75 8.1 7.9 8.1C8.94 8.1 9.91 8.5 10.65 9.24L11.29 9.88L9.87 11.3L9.24 10.66C8.88 10.3 8.4 10.1 7.9 10.1C6.85 10.1 6 10.95 6 12S6.85 13.9 7.9 13.9C8.4 13.9 8.88 13.7 9.24 13.34L13.35 9.24C14.09 8.5 15.06 8.1 16.1 8.1C18.25 8.1 20 9.85 20 12S18.25 15.9 16.1 15.9Z" />
</svg>
大模型
</a>
<a class="{{if eq $.Keyword "AI开发工具"}}active {{end}}item" href="/explore/repos?q=AI开发工具&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "AI开发工具"}}active {{end}}item" href="/explore/repos?q=&topic=AI开发工具&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M11 8H9V10C9 11.1 8.1 12 7 12C8.1 12 9 12.9 9 14V16H11V18H9C7.9 18 7 17.1 7 16V15C7 13.9 6.1 13 5 13V11C6.1 11 7 10.1 7 9V8C7 6.9 7.9 6 9 6H11V8M19 13C17.9 13 17 13.9 17 15V16C17 17.1 16.1 18 15 18H13V16H15V14C15 12.9 15.9 12 17 12C15.9 12 15 11.1 15 10V8H13V6H15C16.1 6 17 6.9 17 8V9C17 10.1 17.9 11 19 11V13Z" />
</svg>
AI开发工具
</a>
<a class="{{if eq $.Keyword "计算机视觉"}}active {{end}}item" href="/explore/repos?q=计算机视觉&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "计算机视觉"}}active {{end}}item" href="/explore/repos?q=&topic=计算机视觉&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" />
</svg>
计算机视觉
</a>
<a class="{{if eq $.Keyword "自然语言处理"}}active {{end}}item" href="/explore/repos?q=自然语言处理&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "自然语言处理"}}active {{end}}item" href="/explore/repos?q=&topic=自然语言处理&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M9,5A4,4 0 0,1 13,9A4,4 0 0,1 9,13A4,4 0 0,1 5,9A4,4 0 0,1 9,5M9,15C11.67,15 17,16.34 17,19V21H1V19C1,16.34 6.33,15 9,15M16.76,5.36C18.78,7.56 18.78,10.61 16.76,12.63L15.08,10.94C15.92,9.76 15.92,8.23 15.08,7.05L16.76,5.36M20.07,2C24,6.05 23.97,12.11 20.07,16L18.44,14.37C21.21,11.19 21.21,6.65 18.44,3.63L20.07,2Z" />
</svg>
自然语言处理
</a>
<a class="{{if eq $.Keyword "机器学习"}}active {{end}}item" href="/explore/repos?q=机器学习&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "机器学习"}}active {{end}}item" href="/explore/repos?q=&topic=机器学习&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M19,12V13.5A4,4 0 0,1 23,17.5C23,18.32 22.75,19.08 22.33,19.71L21.24,18.62C21.41,18.28 21.5,17.9 21.5,17.5A2.5,2.5 0 0,0 19,15V16.5L16.75,14.25L19,12M19,23V21.5A4,4 0 0,1 15,17.5C15,16.68 15.25,15.92 15.67,15.29L16.76,16.38C16.59,16.72 16.5,17.1 16.5,17.5A2.5,2.5 0 0,0 19,20V18.5L21.25,20.75L19,23M12,3C16.42,3 20,4.79 20,7C20,9.21 16.42,11 12,11C7.58,11 4,9.21 4,7C4,4.79 7.58,3 12,3M4,9C4,11.21 7.58,13 12,13C13.11,13 14.17,12.89 15.14,12.68C14.19,13.54 13.5,14.67 13.18,15.96L12,16C7.58,16 4,14.21 4,12V9M20,9V11H19.5L18.9,11.03C19.6,10.43 20,9.74 20,9M4,14C4,16.21 7.58,18 12,18L13,17.97C13.09,19.03 13.42,20 13.95,20.88L12,21C7.58,21 4,19.21 4,17V14Z" />
</svg>
机器学习
</a>
<a class="{{if eq $.Keyword "神经网络"}}active {{end}}item" href="/explore/repos?q=神经网络&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "神经网络"}}active {{end}}item" href="/explore/repos?q=&topic=神经网络&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M13 3C9.23 3 6.19 5.95 6 9.66L4.08 12.19C3.84 12.5 4.08 13 4.5 13H6V16C6 17.11 6.89 18 8 18H9V21H16V16.31C18.37 15.19 20 12.8 20 10C20 6.14 16.88 3 13 3M17.06 9.57L15.1 10.09L16.54 11.54C16.89 11.88 16.89 12.46 16.54 12.81C16.19 13.16 15.61 13.16 15.27 12.81L13.81 11.37L13.3 13.33C13.18 13.82 12.68 14.1 12.21 13.97C11.72 13.84 11.44 13.35 11.57 12.87L12.1 10.9L10.13 11.43C9.65 11.56 9.15 11.28 9.03 10.79C8.9 10.32 9.18 9.82 9.67 9.7L11.63 9.19L10.19 7.73C9.84 7.39 9.84 6.82 10.19 6.46C10.54 6.11 11.12 6.11 11.46 6.46L12.91 7.9L13.43 5.94C13.55 5.46 14.04 5.18 14.5 5.3C15 5.43 15.28 5.92 15.16 6.41L14.63 8.37L16.59 7.84C17.08 7.72 17.57 8 17.7 8.5C17.82 8.96 17.54 9.45 17.06 9.57Z" />
</svg>
神经网络
</a>
<a class="{{if eq $.Keyword "自动驾驶"}}active {{end}}item" href="/explore/repos?q=自动驾驶&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "自动驾驶"}}active {{end}}item" href="/explore/repos?q=&topic=自动驾驶&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M5,14H19L17.5,9.5H6.5L5,14M17.5,19A1.5,1.5 0 0,0 19,17.5A1.5,1.5 0 0,0 17.5,16A1.5,1.5 0 0,0 16,17.5A1.5,1.5 0 0,0 17.5,19M6.5,19A1.5,1.5 0 0,0 8,17.5A1.5,1.5 0 0,0 6.5,16A1.5,1.5 0 0,0 5,17.5A1.5,1.5 0 0,0 6.5,19M18.92,9L21,15V23A1,1 0 0,1 20,24H19A1,1 0 0,1 18,23V22H6V23A1,1 0 0,1 5,24H4A1,1 0 0,1 3,23V15L5.08,9C5.28,8.42 5.85,8 6.5,8H17.5C18.15,8 18.72,8.42 18.92,9M12,0C14.12,0 16.15,0.86 17.65,2.35L16.23,3.77C15.11,2.65 13.58,2 12,2C10.42,2 8.89,2.65 7.77,3.77L6.36,2.35C7.85,0.86 9.88,0 12,0M12,4C13.06,4 14.07,4.44 14.82,5.18L13.4,6.6C13.03,6.23 12.53,6 12,6C11.5,6 10.97,6.23 10.6,6.6L9.18,5.18C9.93,4.44 10.94,4 12,4Z" />
</svg>
自动驾驶
</a>
<a class="{{if eq $.Keyword "机器人"}}active {{end}}item" href="/explore/repos?q=机器人&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "机器人"}}active {{end}}item" href="/explore/repos?q=&topic=机器人&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M12,2A2,2 0 0,1 14,4C14,4.74 13.6,5.39 13,5.73V7H14A7,7 0 0,1 21,14H22A1,1 0 0,1 23,15V18A1,1 0 0,1 22,19H21V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V19H2A1,1 0 0,1 1,18V15A1,1 0 0,1 2,14H3A7,7 0 0,1 10,7H11V5.73C10.4,5.39 10,4.74 10,4A2,2 0 0,1 12,2M7.5,13A2.5,2.5 0 0,0 5,15.5A2.5,2.5 0 0,0 7.5,18A2.5,2.5 0 0,0 10,15.5A2.5,2.5 0 0,0 7.5,13M16.5,13A2.5,2.5 0 0,0 14,15.5A2.5,2.5 0 0,0 16.5,18A2.5,2.5 0 0,0 19,15.5A2.5,2.5 0 0,0 16.5,13Z" />
</svg>
机器人
</a>
<a class="{{if eq $.Keyword "联邦学习"}}active {{end}}item" href="/explore/repos?q=联邦学习&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "联邦学习"}}active {{end}}item" href="/explore/repos?q=&topic=联邦学习&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M3 11H11V3H3M5 5H9V9H5M13 21H21V13H13M15 15H19V19H15M3 21H11V13H3M5 15H9V19H5M13 3V11H21V3M19 9H15V5H19Z" />
</svg>
联邦学习
</a>
<a class="{{if eq $.Keyword "数据挖掘"}}active {{end}}item" href="/explore/repos?q=数据挖掘&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "数据挖掘"}}active {{end}}item" href="/explore/repos?q=&topic=数据挖掘&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M18.36,2.64C20,2.64 21.36,4 21.36,5.64C21.36,7.29 20,8.64 18.36,8.64C16.71,8.64 15.36,7.29 15.36,5.64C15.36,5.34 15.41,5.06 15.5,4.8C14.43,4.29 13.25,4 12,4A8,8 0 0,0 4,12L4.04,12.84L2.05,13.05L2,12A10,10 0 0,1 12,2C13.69,2 15.28,2.42 16.67,3.16C17.16,2.83 17.74,2.64 18.36,2.64M18.36,4.64A1,1 0 0,0 17.36,5.64A1,1 0 0,0 18.36,6.64C18.92,6.64 19.36,6.19 19.36,5.64C19.36,5.08 18.92,4.64 18.36,4.64M5.64,15.36C7.29,15.36 8.64,16.71 8.64,18.36C8.64,18.66 8.59,18.94 8.5,19.2C9.57,19.71 10.75,20 12,20A8,8 0 0,0 20,12L19.96,11.16L21.95,10.95L22,12A10,10 0 0,1 12,22C10.31,22 8.72,21.58 7.33,20.84C6.84,21.17 6.26,21.36 5.64,21.36C4,21.36 2.64,20 2.64,18.36C2.64,16.71 4,15.36 5.64,15.36M5.64,17.36C5.08,17.36 4.64,17.81 4.64,18.36C4.64,18.92 5.08,19.36 5.64,19.36A1,1 0 0,0 6.64,18.36A1,1 0 0,0 5.64,17.36M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8Z" />
</svg>
数据挖掘
</a>
<a class="{{if eq $.Keyword "RISC-V"}}active {{end}}item" href="/explore/repos?q=RISC-V&topic=1&sort={{.SortType}}">
<a class="{{if eq $.Topic "RISC-V"}}active {{end}}item" href="/explore/repos?q=&topic=RISC-V&sort={{.SortType}}">
<svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24">
<path fill="currentColor" d="M17,17H7V7H17M21,11V9H19V7C19,5.89 18.1,5 17,5H15V3H13V5H11V3H9V5H7C5.89,5 5,5.89 5,7V9H3V11H5V13H3V15H5V17A2,2 0 0,0 7,19H9V21H11V19H13V21H15V19H17A2,2 0 0,0 19,17V15H21V13H19V11M13,13H11V11H13M15,9H9V15H15V9Z" />
</svg>


+ 14
- 14
templates/explore/repo_list.tmpl View File

@@ -40,20 +40,20 @@

<div class="ui secondary pointing tabular top attached borderless menu navbar">
{{if .PageIsExplore}}
<a class="{{if eq .SortType "hot"}}active{{end}} item" href="{{$.Link}}?sort=hot&q={{$.Keyword}}&tab={{$.TabName}}">
<a class="{{if eq .SortType "hot"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&topic={{$.Topic}}&sort=hot">
<svg class="svg octicon-repo" width="16" height="16" aria-hidden="true">
<use xlink:href="#octicon-repo" />
</svg>
热门{{.i18n.Tr "explore.repos"}}
</a>
<a class="{{if eq .SortType "active"}}active{{end}} item" href="{{$.Link}}?sort=active&q={{$.Keyword}}&tab={{$.TabName}}">
<a class="{{if eq .SortType "active"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&topic={{$.Topic}}&sort=active">
<svg class="svg octicon-inbox" width="16" height="16" aria-hidden="true">
<use xlink:href="#octicon-inbox" />
</svg>
活跃{{.i18n.Tr "explore.repos"}}
</a>
{{end}}
<a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&tab={{$.TabName}}">
<a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&topic={{$.Topic}}&sort=recentupdate">
<svg class="svg octicon-organization" width="16" height="16" aria-hidden="true">
<use xlink:href="#octicon-organization" />
</svg> {{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}
@@ -67,16 +67,16 @@
<i class="dropdown icon"></i>
</span>
<div class="menu">
<a class="{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
<a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
<a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a>
<a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a>
<a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a>
<a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a>
<a class="{{if eq .SortType "mostforks"}}active{{end}} item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.mostforks"}}</a>
<a class="{{if eq .SortType "fewestforks"}}active{{end}} item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}</a>
<a class="{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
<a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
<a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a>
<a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a>
<a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a>
<a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a>
<a class="{{if eq .SortType "mostforks"}}active{{end}} item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.filter_sort.mostforks"}}</a>
<a class="{{if eq .SortType "fewestforks"}}active{{end}} item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&topic={{$.Topic}}">{{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}</a>
</div>
</div>
</div>
@@ -143,7 +143,7 @@
{{if .Topics }}
<div class="ui tags">
{{range .Topics}}
{{if ne . "" }}<a href="{{AppSubUrl}}/explore/repos?q={{.}}&topic=1"><div class="ui small label topic">{{.}}</div></a>{{end}}
{{if ne . "" }}<a href="{{AppSubUrl}}/explore/repos?q={{.}}&topic={{$.Topic}}"><div class="ui small label topic">{{.}}</div></a>{{end}}
{{end}}
</div>
{{end}}


+ 1
- 1
templates/explore/repo_search.tmpl View File

@@ -9,7 +9,7 @@
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty" style="margin-top:1.2rem">
<div class="ui fluid action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
<input type="hidden" name="tab" value="{{$.TabName}}">
<input type="hidden" name="topic" value="{{$.Topic}}">
<input type="hidden" name="sort" value="{{$.SortType}}">
<button class="ui green button">{{.i18n.Tr "explore.search"}}</button>
</div>


+ 2
- 2
templates/explore/search.tmpl View File

@@ -1,11 +1,11 @@
<div class="repos--seach">
<div class="ui container">
<div class="ui two column centered grid">
<form class="sixteen wide mobile eight fourteen tablet fourteen wide computer column ui form ignore-dirty">
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty">
<div class="ui fluid action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
<input type="hidden" name="tab" value="{{$.TabName}}">
<button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
<button class="ui green button">{{.i18n.Tr "explore.search"}}</button>
</div>
</form>
</div>


+ 1
- 1
templates/org/repo_list.tmpl View File

@@ -58,7 +58,7 @@
{{if .Topics }}
<div class="ui tags">
{{range .Topics}}
{{if ne . "" }}<a href="{{AppSubUrl}}/explore/repos?q={{.}}&topic=1"><div class="ui small label topic">{{.}}</div></a>{{end}}
{{if ne . "" }}<a href="{{AppSubUrl}}/explore/repos?q={{.}}&topic={{$.Topic}}"><div class="ui small label topic">{{.}}</div></a>{{end}}
{{end}}
</div>
{{end}}


+ 70
- 15
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>

<!-- 弹窗 -->
@@ -235,45 +241,91 @@
<div class="ui sixteen wide column">

<!-- 排序区 -->
<div class="ui sixteen wide column">
<!-- <div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
</div>
<!-- <div class="column right aligned">
<div class="column right aligned">
<div class="ui right dropdown type jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i>
</span>
</div>
</div> -->
</div>
</div>
</div>
</div> -->

<!-- 任务展示 -->

<!-- 表头 -->
<div class="dataset list">
<div class="ui grid stackable" style="background: #f0f0f0;;">
<div class="row">
<div class="five wide column">
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span>
</div>
<div class="three wide column">
<span>{{$.i18n.Tr "repo.cloudbrain_status_createtime"}}</span>
</div>
<div class="one wide column">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="seven wide column text center">
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>

</div>
</div>
{{range .Tasks}}
<div class="ui grid stackable item">
<div class="row">

<!-- 任务名 -->
<div class="six wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}">
<span class="fitted">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted">{{.JobName}}</span>
<div class="five wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 15px;">
<span class="fitted" style="vertical-align: middle;">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted text_over" style="width: 90%;vertical-align: middle;margin-left: 0.4rem;">{{.JobName}}</span>
</a>
</div>

<div class="three wide column">
<!--任务状态 -->
<span class="ui compact button job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
{{.Status}}
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
<!-- {{.Status}} -->
<!-- {{if eq .Status "RUNNING"}}
<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>
<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}}
{{.Status}}
{{end}} -->
<span><i style="vertical-align: middle;" class="{{.Status}}"></i><span style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span>
</span>
<!-- <span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" >
{{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> -->
<!-- 任务创建时间 -->
<span class="">{{TimeSinceUnix .CreatedUnix $.Lang}}</span>
<span style="font-size: 12px;margin-left: 0.4rem;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>

<div class="one wide column">
{{if .User.Name}}
<a href="{{AppSubUrl}}/{{.User.Name}}" title="{{.User.Name}}"><img class="ui avatar image" src="{{.User.RelAvatarLink}}"></a>
{{else}}
<a title="Ghost"><img class="ui avatar image" src="{{AppSubUrl}}/user/avatar/Ghost/-1"></a>
{{end}}
</div>
<div class="seven wide column text right">
<div class="ui compact buttons" style="margin-right:10px;">
<div class="ui compact buttons">
{{if and (ne .Status "WAITING") (ne .JobType "DEBUG")}}
<a class="ui basic button" href="{{$.Link}}/{{.JobID}}/rate" target="_blank">
评分
@@ -304,10 +356,10 @@

</div>
<!-- 删除镜像 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{if ne .Status "STOPPED"}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/del{{end}}" method="post">
<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{if not .CanDel}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/del{{end}}" method="post">
{{$.CsrfTokenHtml}}
<a class="ui compact {{if ne .Status "STOPPED"}}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<a class="ui compact {{if not .CanDel}}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
删除
</a>
</form>
@@ -436,15 +488,18 @@
$(document).ready(loadJobStatus);
function loadJobStatus() {
$(".job-status").each((index, job) => {
console.log("---------",index,job)
const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath;
if (job.textContent.trim() == 'STOPPED') {
return
}

$.get(`/api/v1/repos/${repoPath}/cloudbrain/${jobID}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
console.log("status",status)
if (status != job.textContent.trim()) {
//$('#' + jobID).text(status)
//if (status == 'STOPPED') {


+ 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" data-tooltip="{{.Name}}">
<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 text left" data-tooltip='{{$.i18n.Tr "dataset.download_count"}}' data-position="bottom right" style="width: 60px; padding-left: 0;">{{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}}


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

@@ -37,7 +37,7 @@
</div>
{{if .Permission.CanWrite $.UnitTypeDatasets}}
<div class="column four wide right aligned">
<a class="ui button primary" href="javascript:void(0)" id="dataset-edit">
<a class="ui green button" href="javascript:void(0)" id="dataset-edit">
{{.i18n.Tr "dataset.edit"}}
</a>
</div>
@@ -66,7 +66,7 @@
<input name="type" value="{{.Type}}" type="hidden" />
<div class="sixteen wide column">
<a class="ui button" id="cancel">{{.i18n.Tr "cancel"}}</a>
<button class="ui primary button" id="submit">{{.i18n.Tr "dataset.update_dataset"}}</button>
<button class="ui green button" id="submit">{{.i18n.Tr "dataset.update_dataset"}}</button>
</div>
</div>

@@ -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">


+ 53
- 41
templates/repo/datasets/label/index.tmpl View File

@@ -17,9 +17,11 @@
<div class="repository dataset dir-list view">
{{template "repo/header" .}}
<input type="hidden" id="repoId" value="{{.repoId}}">

<div class="ui container">
<div class="header">
<h3 class="modal-title">人工标注任务列表</h3>
<h3 class="modal-title">标注任务列表</h3>
</div>
<div class="ui container">
<div class="ui container">
@@ -45,8 +47,8 @@
</div>

<div class="ui container" style="height: 30px;">
<!-- <button type="button" onclick="setPredictTask();" class="ui blue button" style="float:left">新建自动标注结果图片标注</button> -->
<button type="button" onclick="setDataSetTask();" class="ui blue button" style="float:left">新建数据集图片标注</button>
<!--<button type="button" onclick="setPredictTask();" class="ui blue button" style="float:left">新建图片自动标注</button>-->
<button type="button" onclick="setDataSetTask();" class="ui blue button" style="float:left">新建图片人工标注</button>
<button type="button" onclick="setMultiTaskId();" class="ui blue button" style="float:right;margin-left:20px;">导出标注数据</button>
<button type="button" onclick="countLabel();" class="ui blue button" style="float:right;margin-left:20px;">统计所有标注数量</button>
@@ -65,7 +67,11 @@
<div class="field">
<label for="exampleInputPassword1">选择数据集对象<font color=red>*</font></label>
<select name="pre_predict_task" id="dataset_list" onchange="dataset_sele_Change(this)">
<option value="" selected="">请选择</option>
{{if .Attachments}}
{{range .Attachments}}
<option value="{{.UUID}}">{{.Name}}</option>
{{end}}
{{end}}
</select>
</div>

@@ -105,44 +111,50 @@

<div id="labelModal" class="ui predict modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button aria-hidden="true" data-dismiss="modal" class="close" type="button">×</button>
<h4 class="modal-title">新建自动标注结果人工标注</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="exampleInputPassword1">关联的自动标注任务<font color=red>*</font></label>
<select class="form-control" name="pre_predict_task" id="pre_predict_task_for_label" onchange="sele_Change(this)">
<option value="" selected="">请选择</option>
</select>
<!-- <input type="" class="form-control" id="prepredtaskid" placeholder="预检任务"> -->
</div>
<div class="form-group" >
<label for="exampleInputEmail2">任务指派给</label>
<select class="form-control" name="任务指派给" id="label_assign_user">
<option value=""> 请选择</option>
</select>
</div>
<div class="form-group">
<label for="exampleInputEmail1">人工标注任务名称<font color=red>*</font></label>
<input type="" class="form-control" id="labeltaskname" placeholder="校验标注任务名称,不超过32个字符" maxlength="32">
</div>
<div class="form-group">
<label id = "labelInfo" for="exampleInputFile">标注类别属性,请输入符合格式Json字符串 </label>
<select class="form-control" name="labelpropertytask" id="labelpropertytask_auto">
<option value="" selected="">请选择</option>
</select>
</div>

<i class="close icon"></i>
<div class="header">
<h4 class="modal-title">新建自动标注任务</h4>
</div>
<div class="content">
<div class="ui form">
<div class="field">
<label for="exampleInputPassword1">选择数据集对象<font color=red>*</font></label>
<select name="pre_predict_task" id="dataset_list_auto" onchange="dataset_auto_sele_Change(this)">
{{if .Attachments}}
{{range .Attachments}}
<option value="{{.UUID}}">{{.Name}}</option>
{{end}}
{{end}}
</select>
</div>
<div class="field">
<label for="exampleInputPassword1">选择标注模型<font color=red>*</font></label>
<select name="pre_predict_task" id="model_list">
<option value="" selected="">请选择</option>
</select>
</div>
<div class="field" >
<label for="exampleInputEmail1">自动标注任务名称<font color=red>*</font></label>
<input type="text" id="autolabeltaskname" placeholder="标注任务名称,不超过32个字符">
</div>

<div class="field" style="display:none">
<label for="exampleInputEmail2">任务指派给</label>
<select name="任务指派给" id="label_assign_user">
<option value="">请选择</option>
</select>
</div>

<div class="field" style="display:none">
<label id = "labelInfo" for="exampleInputFile">标注类别(也可以直接在标注界面上进行设置)</label>
<select name="labelpropertytask" id="labelpropertytask_auto" >
<option value="" selected="">请选择</option>
</select>
</div>
<button type="button" onclick="submit_labeltask();">提交</button>
</div>
<button type="button" onclick="submit_autolabeltask();">提交</button>
</div>
</div>
</div> <!-- /.box-body -->


+ 95
- 26
templates/repo/home.tmpl View File

@@ -55,14 +55,48 @@
#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" .}}
<div class="ui container">
{{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,13 +112,14 @@
<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>
<span id="format_prompt">{{.i18n.Tr "repo.topic.format_prompt"}}</span>
</div>
<div class="ui repo-description stackable grid">
<div class="ui repo-description stackable grid">

{{if .RepoSearchEnabled}}
<div class="ui repo-search four wide column">
@@ -101,7 +136,7 @@
</div>
{{end}}
</div>
{{if .Repository.IsArchived}}
<div class="ui warning message">
{{.i18n.Tr "repo.archive.title"}}
@@ -119,6 +154,21 @@
<a href="{{.BaseRepo.Link}}/compare/{{.BaseRepo.DefaultBranch | EscapePound}}...{{if ne .Repository.Owner.Name .BaseRepo.Owner.Name}}{{.Repository.Owner.Name}}:{{end}}{{.BranchName | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button">{{if .PullRequestCtx.Allowed}}{{.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{.i18n.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{if and .Repository.IsFork .PullRequestCtx.Allowed}}
{{if gt .FetchUpstreamCnt 0 }}
<a href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{if .UpstreamSameBranchName}}{{.BranchName | EscapePound}}{{else}}{{.BaseRepo.DefaultBranch | EscapePound}}{{end}}">
<button id="new-pull-request" class="ui compact basic button" title="{{$.i18n.Tr (TrN $.i18n.Lang .FetchUpstreamCnt "repo.pulls.commits_count_1" "repo.pulls.commits_count_n") .FetchUpstreamCnt}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{else if lt .FetchUpstreamCnt 0}}
<a href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{.BaseRepo.DefaultBranch | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button" title="{{.i18n.Tr "repo.pulls.upstream_error"}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{else}}
<a href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{if .UpstreamSameBranchName}}{{.BranchName | EscapePound}}{{else}}{{.BaseRepo.DefaultBranch | EscapePound}}{{end}}">
<button id="new-pull-request" class="ui compact basic button" title="{{.i18n.Tr "repo.pulls.upstream_up_to_date"}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{end}}
{{end}}
</div>
{{end}}
{{else}}
@@ -204,7 +254,7 @@
{{template "repo/view_list" .}}
{{end}}
</div>
<div class="ui six wide tablet four wide computer column">
<div class="ui six wide tablet four wide computer column">
<div id="repo-desc">
<h4 id="about-desc" class="ui header">简介
<!-- <a class="edit-icon" href="javascript:void(0)">
@@ -217,28 +267,47 @@
{{else}}
<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>
{{end}}
</p>

</div>


{{if .Repository.Website}}
<p class="ui">
<i class="gray linkify icon"></i>
<a class="link edit-link" target="_blank" title="{{.Repository.Website}}" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
</p>
{{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>
{{end}}

<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=">{{.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">
<i class="grey code icon"></i>
{{range .LanguageStats}}
@@ -246,14 +315,14 @@
{{end}}
</p>

{{if .LICENSE}}
<p class="ui">
<i class="grey clone icon"></i>
{{.LICENSE}}
</p>
{{end}}

<div class="ui divider"></div>
@@ -269,22 +338,22 @@
{{range .ContributorInfo}}
{{if .UserInfo}}
<a href="{{AppSubUrl}}/{{.UserInfo.Name}}"><img class="ui avatar image" src="{{.UserInfo.RelAvatarLink}}"></a>
{{else if .Email}}
{{else if .Email}}
<a href="mailto:{{.Email}}" class="circular ui button">{{.Email}}</a>
{{end}}
{{end}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

<script type="text/javascript">
$(document).ready(function(){
$(document).ready(function(){
$(".membersmore").click(function(){
$("#contributorInfo > a:nth-child(n+25)").show();
});


+ 18
- 8
templates/repo/issue/labels.tmpl View File

@@ -2,14 +2,24 @@
<div class="repository labels">
{{template "repo/header" .}}
<div class="ui container">
<div class="navbar">
{{template "repo/issue/navbar" .}}
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
<div class="ui right">
<div class="ui green new-label button">{{.i18n.Tr "repo.issues.new_label"}}</div>
</div>
{{end}}
</div>
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.Title | RenderEmoji}}</div>
</div>
</div>
<div class="column right aligned">
{{template "repo/issue/navbar" .}}
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
<div class="ui right">
<div class="ui green new-label button">{{.i18n.Tr "repo.issues.new_label"}}</div>
</div>
{{end}}
</div>
</div>
<div class="ui divider"></div>
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
{{template "repo/issue/labels/label_new" .}}


+ 2
- 2
templates/repo/issue/list.tmpl View File

@@ -3,14 +3,14 @@
{{template "repo/header" .}}
<div class="ui container">
<div class="ui three column stackable grid">
<div class="column">
{{template "repo/issue/navbar" .}}
<div class="column" style="display: flex;align-items: center;">
</div>
<div class="column center aligned">
{{template "repo/issue/search" .}}
</div>
{{if not .Repository.IsArchived}}
<div class="column right aligned">
{{template "repo/issue/navbar" .}}
{{if .PageIsIssueList}}
<a class="ui green button" href="{{.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a>
{{else}}


+ 8
- 2
templates/repo/issue/milestone_issues.tmpl View File

@@ -3,8 +3,14 @@
{{template "repo/header" .}}
<div class="ui container">
<div class="ui three column stackable grid">
<div class="column">
<h3>{{.Milestone.Name}}</h3>
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<a class="section" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.Milestone.Name}}</div>
</div>
</div>
<div class="column center aligned">



+ 22
- 6
templates/repo/issue/milestone_new.tmpl View File

@@ -2,13 +2,29 @@
<div class="repository new milestone">
{{template "repo/header" .}}
<div class="ui container">
<div class="navbar">
{{template "repo/issue/navbar" .}}
{{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}}
<div class="ui right floated secondary menu">
<a class="ui green button" href="{{$.RepoLink}}/milestones/new">{{.i18n.Tr "repo.milestones.new"}}</a>
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<a class="section" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a>
<div class="divider"> / </div>
{{if .PageIsEditMilestone}}
<div class="action section">{{.i18n.Tr "repo.milestones.edit"}}</div>
{{else}}
<div class="action section">{{.i18n.Tr "repo.milestones.new"}}</div>
{{end}}
</div>
</div>
{{end}}
<div class="column right aligned">
{{template "repo/issue/navbar" .}}
{{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}}
<div class="ui right floated secondary menu">
<a class="ui green button" href="{{$.RepoLink}}/milestones/new">{{.i18n.Tr "repo.milestones.new"}}</a>
</div>
{{end}}
</div>
</div>
<div class="ui divider"></div>
<h2 class="ui dividing header">


+ 19
- 8
templates/repo/issue/milestones.tmpl View File

@@ -2,14 +2,25 @@
<div class="repository milestones">
{{template "repo/header" .}}
<div class="ui container">
<div class="navbar">
{{template "repo/issue/navbar" .}}
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
<div class="ui right">
<a class="ui green button" href="{{$.Link}}/new">{{.i18n.Tr "repo.milestones.new"}}</a>
</div>
{{end}}
</div>
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.Title | RenderEmoji}}</div>
</div>
</div>
{{if not .Repository.IsArchived}}
<div class="column right aligned">
{{template "repo/issue/navbar" .}}
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
<div class="ui right">
<a class="ui green button" href="{{$.Link}}/new">{{.i18n.Tr "repo.milestones.new"}}</a>
</div>
{{end}}
</div>
{{end}}
</div>
<div class="ui divider"></div>
{{template "base/alert" .}}
<div class="ui tiny basic buttons">


+ 1
- 1
templates/repo/issue/navbar.tmpl View File

@@ -1,4 +1,4 @@
<div class="ui compact left small menu">
<div class="ui compact small menu" style="margin-right: 1rem;">
<a class="{{if .PageIsLabels}}active{{end}} item" href="{{.RepoLink}}/labels">{{.i18n.Tr "repo.labels"}}</a>
<a class="{{if .PageIsMilestones}}active{{end}} item" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a>
</div>

+ 15
- 3
templates/repo/issue/new.tmpl View File

@@ -2,9 +2,21 @@
<div class="repository new issue">
{{template "repo/header" .}}
<div class="ui container">
<div class="navbar">
{{template "repo/issue/navbar" .}}
</div>
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues.new"}}</div>
</div>
</div>
<div class="column right aligned">
{{template "repo/issue/navbar" .}}
</div>
</div>
<div class="ui divider"></div>
{{template "repo/issue/new_form" .}}
</div>


+ 1
- 1
templates/repo/issue/search.tmpl View File

@@ -7,7 +7,7 @@
<input type="hidden" name="assignee" value="{{$.AssigneeID}}"/>
<div class="ui search action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
<button class="ui blue button" type="submit">{{.i18n.Tr "explore.search"}}</button>
<button class="ui green button" type="submit">{{.i18n.Tr "explore.search"}}</button>
</div>
</div>
</form>

+ 15
- 2
templates/repo/issue/view.tmpl View File

@@ -3,11 +3,24 @@
{{template "repo/header" .}}
<div class="ui container">
<div class="ui two column stackable grid">
<div class="column">
{{template "repo/issue/navbar" .}}
<div class="column" style="display: flex;align-items: center;">
{{if .PageIsIssueList}}
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div>
</div>
{{else}}
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div>
</div>
{{end}}
</div>
{{if not .Repository.IsArchived}}
<div class="column right aligned">
{{template "repo/issue/navbar" .}}
{{if .PageIsIssueList}}
<a class="ui green button" href="{{.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a>
{{else}}


+ 47
- 13
templates/repo/modelarts/index.tmpl View File

@@ -228,41 +228,75 @@
<div class="ui sixteen wide column">

<!-- 排序区 -->
<div class="ui sixteen wide column">
<!-- <div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
</div>
<!-- <div class="column right aligned">
<div class="column right aligned">
<div class="ui right dropdown type jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i>
</span>
</div>
</div> -->
</div>
</div>
</div>
</div> -->

<!-- 任务展示 -->
<div class="dataset list">

<!-- 表头 -->
<div class="ui grid stackable" style="background: #f0f0f0;;">
<div class="row">
<div class="five wide column">
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span>
</div>
<div class="three wide column">
<span>{{$.i18n.Tr "repo.cloudbrain_status_createtime"}}</span>
</div>
<div class="one wide column">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="seven wide column text center">
<span style="margin-left: 10rem;">{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>

</div>
</div>



{{range .Tasks}}
<div class="ui grid stackable item">
<div class="row">
<!-- 任务名 -->
<div class="six wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}">
<span class="fitted">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted">{{.JobName}}</span>
<div class="five wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 15px;">
<span class="fitted" style="vertical-align: middle;">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted" style="width: 90%;vertical-align: middle;margin-left: 0.4rem;">{{.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 class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
<span><i style="vertical-align: middle;" class="{{.Status}}"></i><span style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span>
</span>
<!-- 任务创建时间 -->
<span class="">{{TimeSinceUnix .CreatedUnix $.Lang}}</span>
<span style="font-size: 12px;margin-left: 0.4rem;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>

<div class="one wide column">
{{if .User.Name}}
<a href="{{AppSubUrl}}/{{.User.Name}}" title="{{.User.Name}}"><img class="ui avatar image" src="{{.User.RelAvatarLink}}"></a>
{{else}}
<a title="Ghost"><img class="ui avatar image" src="{{AppSubUrl}}/user/avatar/Ghost/-1"></a>
{{end}}
</div>

<div class="seven wide column text right">
@@ -270,7 +304,7 @@
<a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}">
查看
</a>
<a class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.Link}}/{{.JobID}}/debug">
<a class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.Link}}/{{.JobID}}/debug" target="_blank">
调试
</a>
<form id="stopForm-{{.JobID}}" action="{{if ne .Status "RUNNING"}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/stop{{end}}" method="post" style="margin-left:-1px;">
@@ -282,9 +316,9 @@
</div>

<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{if ne .Status "STOPPED"}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/del{{end}}" method="post">
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{if not .CanDel}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/del{{end}}" method="post">
{{$.CsrfTokenHtml}}
<a class="ui compact {{if ne .Status "STOPPED"}}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<a class="ui compact {{if not .CanDel}}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
删除
</a>
</form>


+ 15
- 1
templates/repo/pulls/commits.tmpl View File

@@ -2,11 +2,25 @@
<div class="repository view issue pull commits">
{{template "repo/header" .}}
<div class="ui container">
<div class="navbar">
<!-- <div class="navbar">
{{template "repo/issue/navbar" .}}
<div class="ui right">
<a class="ui green button {{if not .PullRequestCtx.Allowed}}disabled{{end}}" href="{{.RepoLink}}/compare/{{.BranchName | EscapePound}}...{{.PullRequestCtx.HeadInfo | EscapePound}}">{{.i18n.Tr "repo.pulls.new"}}</a>
</div>
</div> -->
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div>
</div>
</div>
<div class="column right aligned">
{{template "repo/issue/navbar" .}}
<a class="ui green button {{if not .PullRequestCtx.Allowed}}disabled{{end}}" href="{{.RepoLink}}/compare/{{.BranchName | EscapePound}}...{{.PullRequestCtx.HeadInfo | EscapePound}}">{{.i18n.Tr "repo.pulls.new"}}</a>
</div>
</div>
<div class="ui divider"></div>
{{template "repo/issue/view_title" .}}


+ 15
- 1
templates/repo/pulls/files.tmpl View File

@@ -2,11 +2,25 @@
<div class="repository view issue pull files diff">
{{template "repo/header" .}}
<div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}">
<div class="navbar">
<!-- <div class="navbar">
{{template "repo/issue/navbar" .}}
<div class="ui right">
<a class="ui green button {{if not .PullRequestCtx.Allowed}}disabled{{end}}" href="{{.RepoLink}}/compare/{{.BranchName | EscapePound}}...{{.PullRequestCtx.HeadInfo | EscapePound}}">{{.i18n.Tr "repo.pulls.new"}}</a>
</div>
</div> -->
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div>
</div>
</div>
<div class="column right aligned">
{{template "repo/issue/navbar" .}}
<a class="ui green button {{if not .PullRequestCtx.Allowed}}disabled{{end}}" href="{{.RepoLink}}/compare/{{.BranchName | EscapePound}}...{{.PullRequestCtx.HeadInfo | EscapePound}}">{{.i18n.Tr "repo.pulls.new"}}</a>
</div>
</div>
<div class="ui divider"></div>
{{template "repo/issue/view_title" .}}


+ 1
- 1
templates/user/dashboard/issues.tmpl View File

@@ -74,7 +74,7 @@
<input type="hidden" name="state" value="{{$.State}}"/>
<div class="ui search action input">
<input name="q" value="{{$.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
<button class="ui blue button" type="submit">{{.i18n.Tr "explore.search"}}</button>
<button class="ui green button" type="submit">{{.i18n.Tr "explore.search"}}</button>
</div>
</div>
</form>


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

@@ -0,0 +1,491 @@
<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]){
if(this.arrayTopics.includes(array.topic_name)){
return
}
else{
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){
$('#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
});
let findelement = this.array.some((item)=>{
return item.topic_name===this.input
})
this.showInputValue = !findelement
})
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=`
);
link.text(topicArray[i]);
// link.insertBefore(last);
viewDiv.append(link)
}
}
viewDiv.show();
})
},
postTopic(){
if(!this.showInputValue){
return
}
const patter = /^[\u4e00-\u9fa5a-zA-Z0-9][\u4e00-\u9fa5a-zA-Z0-9-]{0,34}$/
let regexp = patter.test(this.input)
if(!regexp){
this.$notify({
message: '标签名必须以中文、字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符',
duration: 3000,
type:'error'
});
return
}else{
let topic = this.input
if(this.arrayTopics.includes(topic)){
return
}
else{
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){
$('#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
this.showAddTopic = false
}
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;}
.STOPPED{display:inline-block;width:18px;height:18px;background:url("/img/icons.svg");background-position: -496px -52px;background-position: -459px -52px;}
.RUNNING{display:inline-block;width:18px;height:18px;background:url("/img/icons.svg");background-position: -496px -52px;background-position: -478px -52px;}
.i-bg-orange{background-position: -495px -51px;}
.FAILED{display:inline-block;width:18px;height:18px;background:url("/img/icons.svg");background-position: -496px -52px;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;}
.CREATING, .STOPPING, .DELETING, .STARTING, .WAITING{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