Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/560pull/562/head
@@ -385,6 +385,17 @@ CONN_MAX_LIFETIME = 3s | |||||
; Database maximum number of open connections, default is 0 meaning no maximum | ; Database maximum number of open connections, default is 0 meaning no maximum | ||||
MAX_OPEN_CONNS = 0 | 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] | [indexer] | ||||
; Issue indexer type, currently support: bleve, db or elasticsearch, default is bleve | ; Issue indexer type, currently support: bleve, db or elasticsearch, default is bleve | ||||
ISSUE_INDEXER_TYPE = bleve | ISSUE_INDEXER_TYPE = bleve | ||||
@@ -52,6 +52,7 @@ require ( | |||||
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 | github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 | ||||
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 | github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 | ||||
github.com/golang/protobuf v1.4.1 // indirect | 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/google/go-github/v24 v24.0.1 | ||||
github.com/gorilla/context v1.1.1 | github.com/gorilla/context v1.1.1 | ||||
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect | github.com/hashicorp/go-retryablehttp v0.6.6 // indirect | ||||
@@ -464,3 +464,12 @@ func CanDelAttachment(isSigned bool, user *User, attach *Attachment) bool { | |||||
} | } | ||||
return false | 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 | |||||
} |
@@ -5,11 +5,12 @@ import ( | |||||
"fmt" | "fmt" | ||||
"strings" | "strings" | ||||
"time" | "time" | ||||
"xorm.io/builder" | |||||
"xorm.io/xorm" | "xorm.io/xorm" | ||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"code.gitea.io/gitea/modules/timeutil" | "code.gitea.io/gitea/modules/timeutil" | ||||
"xorm.io/builder" | |||||
) | ) | ||||
type CloudbrainStatus string | type CloudbrainStatus string | ||||
@@ -59,12 +60,18 @@ type Cloudbrain struct { | |||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | ||||
DeletedAt time.Time `xorm:"deleted"` | DeletedAt time.Time `xorm:"deleted"` | ||||
CanDebug bool `xorm:"-"` | CanDebug bool `xorm:"-"` | ||||
CanDel bool `xorm:"-"` | |||||
Type int `xorm:"INDEX DEFAULT 0"` | Type int `xorm:"INDEX DEFAULT 0"` | ||||
User *User `xorm:"-"` | User *User `xorm:"-"` | ||||
Repo *Repository `xorm:"-"` | Repo *Repository `xorm:"-"` | ||||
} | } | ||||
type CloudbrainInfo struct { | |||||
Cloudbrain `xorm:"extends"` | |||||
User `xorm:"extends"` | |||||
} | |||||
type CloudBrainLoginResult struct { | type CloudBrainLoginResult struct { | ||||
Code string | Code string | ||||
Msg string | Msg string | ||||
@@ -523,7 +530,7 @@ type NotebookDelResult struct { | |||||
InstanceID string `json:"instance_id"` | InstanceID string `json:"instance_id"` | ||||
} | } | ||||
func Cloudbrains(opts *CloudbrainsOptions) ([]*Cloudbrain, int64, error) { | |||||
func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { | |||||
sess := x.NewSession() | sess := x.NewSession() | ||||
defer sess.Close() | defer sess.Close() | ||||
@@ -583,8 +590,10 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*Cloudbrain, int64, error) { | |||||
} | } | ||||
sess.OrderBy("cloudbrain.created_unix DESC") | 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) | return nil, 0, fmt.Errorf("Find: %v", err) | ||||
} | } | ||||
sess.Close() | sess.Close() | ||||
@@ -620,6 +629,18 @@ func GetCloudbrainByJobID(jobID string) (*Cloudbrain, error) { | |||||
return getRepoCloudBrain(cb) | 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) { | func SetCloudbrainStatusByJobID(jobID string, status CloudbrainStatus) (err error) { | ||||
cb := &Cloudbrain{JobID: jobID, Status: string(status)} | cb := &Cloudbrain{JobID: jobID, Status: string(status)} | ||||
_, err = x.Cols("status").Where("cloudbrain.job_id=?", jobID).Update(cb) | _, 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} | cb := &Cloudbrain{JobName: jobName} | ||||
return getRepoCloudBrain(cb) | 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 | |||||
} |
@@ -1016,3 +1016,19 @@ func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID | |||||
}) | }) | ||||
return err | 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 | |||||
} |
@@ -59,6 +59,9 @@ var ( | |||||
x *xorm.Engine | x *xorm.Engine | ||||
tables []interface{} | tables []interface{} | ||||
xStatistic *xorm.Engine | |||||
tablesStatistic []interface{} | |||||
// HasEngine specifies if we have a xorm.Engine | // HasEngine specifies if we have a xorm.Engine | ||||
HasEngine bool | HasEngine bool | ||||
) | ) | ||||
@@ -132,14 +135,19 @@ func init() { | |||||
new(RecommendOrg), | new(RecommendOrg), | ||||
) | ) | ||||
tablesStatistic = append(tablesStatistic, | |||||
new(RepoStatistic), | |||||
new(UserBusinessAnalysis), | |||||
) | |||||
gonicNames := []string{"SSL", "UID"} | gonicNames := []string{"SSL", "UID"} | ||||
for _, name := range gonicNames { | for _, name := range gonicNames { | ||||
names.LintGonicMapper[name] = true | 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 { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
@@ -153,14 +161,12 @@ func getEngine() (*xorm.Engine, error) { | |||||
} | } | ||||
engine.SetSchema(setting.Database.Schema) | engine.SetSchema(setting.Database.Schema) | ||||
HasEngine = true | |||||
return engine, nil | return engine, nil | ||||
} | } | ||||
// NewTestEngine sets a new test xorm.Engine | // NewTestEngine sets a new test xorm.Engine | ||||
func NewTestEngine(x *xorm.Engine) (err error) { | func NewTestEngine(x *xorm.Engine) (err error) { | ||||
x, err = getEngine() | |||||
x, err = getEngine(setting.Database) | |||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("Connect to database: %v", err) | 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...) | 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) { | func SetEngine() (err error) { | ||||
x, err = getEngine() | |||||
x, err = getEngine(setting.Database) | |||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("Failed to connect to database: %v", err) | 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 | return nil | ||||
} | } | ||||
// NewEngine initializes a new xorm.Engine | |||||
func NewEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) { | 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 | return err | ||||
} | } | ||||
x.SetDefaultContext(ctx) | |||||
engine.SetDefaultContext(ctx) | |||||
if err = x.Ping(); err != nil { | |||||
if err = engine.Ping(); err != nil { | |||||
return err | return err | ||||
} | } | ||||
if err = migrateFunc(x); err != nil { | |||||
if err = migrateFunc(engine); err != nil { | |||||
return fmt.Errorf("migrate: %v", err) | 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) | return fmt.Errorf("sync database struct error: %v", err) | ||||
} | } | ||||
@@ -257,6 +300,11 @@ func Ping() error { | |||||
if x != nil { | if x != nil { | ||||
return x.Ping() | return x.Ping() | ||||
} | } | ||||
if xStatistic != nil { | |||||
return xStatistic.Ping() | |||||
} | |||||
return errors.New("database not configured") | return errors.New("database not configured") | ||||
} | } | ||||
@@ -1424,6 +1424,12 @@ func GetAllRepositories() ([]*Repository, error) { | |||||
return getALLRepositories(x) | 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) { | func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) { | ||||
repo.LowerName = strings.ToLower(repo.Name) | repo.LowerName = strings.ToLower(repo.Name) | ||||
@@ -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 | |||||
} |
@@ -166,6 +166,8 @@ type SearchRepoOptions struct { | |||||
Archived util.OptionalBool | Archived util.OptionalBool | ||||
// only search topic name | // only search topic name | ||||
TopicOnly bool | TopicOnly bool | ||||
//search by Specific TopicName | |||||
TopicName string | |||||
// include description in keyword search | // include description in keyword search | ||||
IncludeDescription bool | IncludeDescription bool | ||||
// None -> include has milestones AND has no milestone | // None -> include has milestones AND has no milestone | ||||
@@ -327,6 +329,18 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { | |||||
} | } | ||||
cond = cond.And(keywordCond) | 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 { | if opts.Fork != util.OptionalBoolNone { | ||||
cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) | cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) | ||||
@@ -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) | |||||
} |
@@ -26,10 +26,11 @@ const ( | |||||
// Watch is connection request for receiving repository notification. | // Watch is connection request for receiving repository notification. | ||||
type Watch struct { | 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 | // getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found | ||||
@@ -4,11 +4,14 @@ | |||||
package models | package models | ||||
import "code.gitea.io/gitea/modules/timeutil" | |||||
// Star represents a starred repo by an user. | // Star represents a starred repo by an user. | ||||
type Star struct { | 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. | // StarRepo or unstar repository. | ||||
@@ -39,7 +42,7 @@ func StarRepo(userID, repoID int64, star bool) error { | |||||
return nil | 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 | return err | ||||
} | } | ||||
if _, err := sess.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID); err != nil { | 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 { | 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 | return has | ||||
} | } | ||||
@@ -1556,6 +1556,18 @@ func GetUserByActivateEmail(email string) (*User, error) { | |||||
if len(users) >= 1 { | if len(users) >= 1 { | ||||
return &users[0],nil | return &users[0],nil | ||||
}else { | }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") | return nil, errors.New("cannot find user by email") | ||||
} | } | ||||
} | } | ||||
@@ -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 | |||||
} |
@@ -4,11 +4,14 @@ | |||||
package models | package models | ||||
import "code.gitea.io/gitea/modules/timeutil" | |||||
// Follow represents relations of user and his/her followers. | // Follow represents relations of user and his/her followers. | ||||
type Follow struct { | 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. | // IsFollowing returns true if user is following followID. | ||||
@@ -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() { | func initBasicTasks() { | ||||
registerUpdateMirrorTask() | registerUpdateMirrorTask() | ||||
registerRepoHealthCheck() | registerRepoHealthCheck() | ||||
@@ -177,4 +199,7 @@ func initBasicTasks() { | |||||
registerHandleBlockChainUnSuccessRepos() | registerHandleBlockChainUnSuccessRepos() | ||||
registerHandleBlockChainMergedPulls() | registerHandleBlockChainMergedPulls() | ||||
registerHandleBlockChainUnSuccessCommits() | registerHandleBlockChainUnSuccessCommits() | ||||
registerHandleRepoStatistic() | |||||
registerHandleUserStatistic() | |||||
} | } |
@@ -206,6 +206,15 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { | |||||
return commits.Front().Value.(*Commit), nil | 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 | // CommitsRangeSize the default commits range size | ||||
var CommitsRangeSize = 50 | var CommitsRangeSize = 50 | ||||
@@ -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 | |||||
} |
@@ -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 | |||||
} |
@@ -24,111 +24,119 @@ var ( | |||||
EnableSQLite3 bool | EnableSQLite3 bool | ||||
// Database holds the database settings | // 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 | // GetDBTypeByName returns the dataase type as it defined on XORM according the given name | ||||
func GetDBTypeByName(name string) string { | func GetDBTypeByName(name string) string { | ||||
return dbTypes[name] | 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": | case "sqlite3": | ||||
Database.UseSQLite3 = true | |||||
database.UseSQLite3 = true | |||||
case "mysql": | case "mysql": | ||||
Database.UseMySQL = true | |||||
database.UseMySQL = true | |||||
case "postgres": | case "postgres": | ||||
Database.UsePostgreSQL = true | |||||
database.UsePostgreSQL = true | |||||
case "mssql": | 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 { | } 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 | // DBConnStr returns database connection string | ||||
func DBConnStr() (string, error) { | |||||
func DBConnStr(database *DBInfo) (string, error) { | |||||
connStr := "" | connStr := "" | ||||
var Param = "?" | var Param = "?" | ||||
if strings.Contains(Database.Name, Param) { | |||||
if strings.Contains(database.Name, Param) { | |||||
Param = "&" | Param = "&" | ||||
} | } | ||||
switch Database.Type { | |||||
switch database.Type { | |||||
case "mysql": | case "mysql": | ||||
connType := "tcp" | connType := "tcp" | ||||
if Database.Host[0] == '/' { // looks like a unix socket | |||||
if database.Host[0] == '/' { // looks like a unix socket | |||||
connType = "unix" | connType = "unix" | ||||
} | } | ||||
tls := Database.SSLMode | |||||
tls := database.SSLMode | |||||
if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL | if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL | ||||
tls = "false" | tls = "false" | ||||
} | } | ||||
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s", | 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": | 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": | 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": | case "sqlite3": | ||||
if !EnableSQLite3 { | if !EnableSQLite3 { | ||||
return "", errors.New("this binary version does not build support for SQLite3") | 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) | 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: | default: | ||||
return "", fmt.Errorf("Unknown database type: %s", Database.Type) | |||||
return "", fmt.Errorf("Unknown database type: %s", database.Type) | |||||
} | } | ||||
return connStr, nil | return connStr, nil | ||||
@@ -482,6 +482,14 @@ var ( | |||||
PoolInfos string | PoolInfos string | ||||
Flavor string | Flavor string | ||||
FlavorInfos 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. | // DateLang transforms standard language locale name to corresponding value in datetime plugin. | ||||
@@ -1201,6 +1209,14 @@ func NewContext() { | |||||
PoolInfos = sec.Key("POOL_INFOS").MustString("") | PoolInfos = sec.Key("POOL_INFOS").MustString("") | ||||
Flavor = sec.Key("FLAVOR").MustString("") | Flavor = sec.Key("FLAVOR").MustString("") | ||||
FlavorInfos = sec.Key("FLAVOR_INFOS").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 { | func loadInternalToken(sec *ini.Section) string { | ||||
@@ -87,7 +87,7 @@ write = Write | |||||
preview = Preview | preview = Preview | ||||
loading = Loading… | loading = Loading… | ||||
error404_index = Request forbidden by administrative rules | |||||
error404_index = Request forbidden by administrative rules | |||||
error500_index = Internal Server Error | 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. | 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. | 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_key = Revoke | ||||
revoke_oauth2_grant = Revoke Access | 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_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_desc = Two-factor authentication enhances the security of your account. | ||||
twofa_is_enrolled = Your account is currently <strong>enrolled</strong> in two-factor authentication. | 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: | cloudbrain_platform_selection = Select the cloudbrain platform you want to use: | ||||
confirm_choice = confirm | confirm_choice = confirm | ||||
cloudbran1_tips = Only data in zip format can create cloudbrain tasks | 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.items = Template Items | ||||
template.git_content = Git Content (Default Branch) | template.git_content = Git Content (Default Branch) | ||||
@@ -831,6 +835,7 @@ fork = Fork | |||||
download_archive = Download Repository | download_archive = Download Repository | ||||
no_desc = No Description | no_desc = No Description | ||||
no_label = No labels | |||||
quick_guide = Quick Guide | quick_guide = Quick Guide | ||||
clone_this_repo = Clone this repository | clone_this_repo = Clone this repository | ||||
create_new_repo_command = Creating a new repository on the command line | 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 | branches = Branches | ||||
tags = Tags | tags = Tags | ||||
issues = Issues | issues = Issues | ||||
issues_detail = Detail | |||||
pulls = Pull Requests | pulls = Pull Requests | ||||
labels = Labels | labels = Labels | ||||
org_labels_desc = Organization level labels that can be used with <strong>all repositories</strong> under this organization | 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.reject_count_n = "%d change requests" | ||||
pulls.waiting_count_1 = "%d waiting review" | pulls.waiting_count_1 = "%d waiting review" | ||||
pulls.waiting_count_n = "%d waiting reviews" | 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_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. | pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. | ||||
@@ -772,6 +772,10 @@ cloudbrain_selection=云脑选择 | |||||
cloudbrain_platform_selection=选择您准备使用的云脑平台: | cloudbrain_platform_selection=选择您准备使用的云脑平台: | ||||
confirm_choice=确定 | confirm_choice=确定 | ||||
cloudbran1_tips=只有zip格式的数据集才能发起云脑任务 | cloudbran1_tips=只有zip格式的数据集才能发起云脑任务 | ||||
cloudbrain_creator=创建者 | |||||
cloudbrain_task=任务名称 | |||||
cloudbrain_operate=操作 | |||||
cloudbrain_status_createtime=状态/创建时间 | |||||
template.items=模板选项 | template.items=模板选项 | ||||
template.git_content=Git数据(默认分支) | template.git_content=Git数据(默认分支) | ||||
@@ -833,6 +837,7 @@ fork=派生 | |||||
download_archive=下载此项目 | download_archive=下载此项目 | ||||
no_desc=暂无描述 | no_desc=暂无描述 | ||||
no_label = 暂无标签 | |||||
quick_guide=快速帮助 | quick_guide=快速帮助 | ||||
clone_this_repo=克隆当前项目 | clone_this_repo=克隆当前项目 | ||||
create_new_repo_command=从命令行创建一个新的项目 | create_new_repo_command=从命令行创建一个新的项目 | ||||
@@ -847,6 +852,7 @@ filter_branch_and_tag=过滤分支或标签 | |||||
branches=分支列表 | branches=分支列表 | ||||
tags=标签列表 | tags=标签列表 | ||||
issues=任务 | issues=任务 | ||||
issues_detail=详情 | |||||
pulls=合并请求 | pulls=合并请求 | ||||
labels=标签 | labels=标签 | ||||
org_labels_desc=组织级别的标签,可以被本组织下的 <strong>所有项目</strong> 使用 | org_labels_desc=组织级别的标签,可以被本组织下的 <strong>所有项目</strong> 使用 | ||||
@@ -1243,6 +1249,11 @@ pulls.reject_count_1=%d 变更请求 | |||||
pulls.reject_count_n=%d 变更请求 | pulls.reject_count_n=%d 变更请求 | ||||
pulls.waiting_count_1=%d 个正在等待审核 | pulls.waiting_count_1=%d 个正在等待审核 | ||||
pulls.waiting_count_n=%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_desc=由于未启用合并选项,此合并请求无法被合并。 | ||||
pulls.no_merge_helper=在项目设置中启用合并选项或者手工合并请求。 | pulls.no_merge_helper=在项目设置中启用合并选项或者手工合并请求。 | ||||
@@ -23,7 +23,7 @@ var pageSize = 10; | |||||
var tableData; | var tableData; | ||||
var tablePageData; | var tablePageData; | ||||
var preDictTaskData; | |||||
var modelListData; | |||||
var dataSetTaskData; | var dataSetTaskData; | ||||
var userInfoData; | var userInfoData; | ||||
@@ -35,8 +35,8 @@ console.log("repoId=" + repoId); | |||||
function setDataSetTask(){ | function setDataSetTask(){ | ||||
dataset_task_list(); | |||||
display_createdatasetlabel(0); | |||||
//dataset_task_list(); | |||||
//display_createdatasetlabel(0); | |||||
//getUser(); | //getUser(); | ||||
//dislpayUser(); | //dislpayUser(); | ||||
getLabelPropertyTask(); | 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(){ | 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({ | $.ajax({ | ||||
type:"GET", | type:"GET", | ||||
url:ip + "/api/pre-predict-taskforLabel/", | |||||
url:ip + "/api/queryAlgModelForAutoLabel/", | |||||
headers: { | headers: { | ||||
authorization:token, | authorization:token, | ||||
}, | }, | ||||
dataType:"json", | dataType:"json", | ||||
async:false, | async:false, | ||||
success:function(json){ | success:function(json){ | ||||
preDictTaskData = json; | |||||
modelListData = json; | |||||
console.log(json); | console.log(json); | ||||
// return json.token; | // 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){ | function sele_export_Change(sele){ | ||||
var isNeedPicture = $('#isNeedPicture option:selected').val(); | var isNeedPicture = $('#isNeedPicture option:selected').val(); | ||||
@@ -201,27 +200,13 @@ function dataset_sele_Change(sele){ | |||||
$("#datasetlabeltaskname").attr({value:dataset_listName+"-人工标注"}); | $("#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; | var createsucced; | ||||
function submit_datasettask(){ | function submit_datasettask(){ | ||||
@@ -242,7 +227,7 @@ function submit_datasettask(){ | |||||
} | } | ||||
var labelpropertytaskid = $('#labelpropertytask_dataset option:selected').val(); | var labelpropertytaskid = $('#labelpropertytask_dataset option:selected').val(); | ||||
createsucced = true; | 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){ | if(createsucced){ | ||||
$(".ui.dataset.modal").modal("hide"); | $(".ui.dataset.modal").modal("hide"); | ||||
//$("#labelDataModal").modal('hide'); | //$("#labelDataModal").modal('hide'); | ||||
@@ -250,25 +235,30 @@ function submit_datasettask(){ | |||||
page(0,pageSize); | 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){ | if (isEmpty(task_name) || task_name.length > 32){ | ||||
alert("人工标注任务名称不能为空或者不能超过32个字符。"); | |||||
alert("自动标注任务名称不能为空或者不能超过32个字符。"); | |||||
return; | 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)){ | if(isEmpty(relate_task_id)){ | ||||
alert("关联的自动标注任务不能为空。"); | |||||
alert("数据集对象不能为空。"); | |||||
return; | return; | ||||
} | } | ||||
var assign_user_id = $('#label_assign_user option:selected').val(); | var assign_user_id = $('#label_assign_user option:selected').val(); | ||||
if(isEmpty(assign_user_id)){ | if(isEmpty(assign_user_id)){ | ||||
assign_user_id = token; | assign_user_id = token; | ||||
} | } | ||||
var labelpropertytaskid = $('#labelpropertytask_dataset option:selected').val(); | |||||
var labelpropertytaskid = $('#labelpropertytask_auto option:selected').val(); | |||||
createsucced = true; | 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){ | if(createsucced){ | ||||
$("#labelModal").modal('hide'); | $("#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 = []; | var relate_other_label_task = []; | ||||
if(task_flow_type == 2){ | 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, | 'assign_user_id':assign_user_id, | ||||
'task_flow_type':task_flow_type, | 'task_flow_type':task_flow_type, | ||||
'relate_task_id':relate_task_id,//task id | '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, | "taskType": taskType, | ||||
"appid": repoId, | |||||
"createUserName":userName, | |||||
"labelPropertyTaskId":labelpropertytaskid | |||||
"appid": repoId, | |||||
"createUserName":userName, | |||||
"labelPropertyTaskId":labelpropertytaskid, | |||||
"modelId":model_id | |||||
}), | }), | ||||
success:function(res){ | success:function(res){ | ||||
console.log(res); | console.log(res); | ||||
if(res.code == 0){ | if(res.code == 0){ | ||||
alert("人工标注任务创建成功!"); | |||||
alert("自动标注任务创建成功!"); | |||||
createsucced = true; | createsucced = true; | ||||
} | } | ||||
else{ | else{ | ||||
alert("创建人工标注任务失败," + res.message); | |||||
alert("创建自动标注任务失败," + res.message); | |||||
createsucced = false; | createsucced = false; | ||||
} | } | ||||
}, | }, | ||||
@@ -433,11 +424,11 @@ function delete_labeltask(){ | |||||
return; | return; | ||||
} | } | ||||
var Check = $("table[id='label_task_list'] input[type=checkbox]:checked");//在table中找input下类型为checkbox属性为选中状态的数据 | 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); | page(0,pageSize); | ||||
} | } | ||||
@@ -480,13 +471,13 @@ function delete_labeltask_byid(label_task_id){ | |||||
function getTaskTypeDesc(task_type){ | function getTaskTypeDesc(task_type){ | ||||
if(task_type == 1){ | if(task_type == 1){ | ||||
return "自动标注结果"; | |||||
return "图片-自动标注"; | |||||
}else if(task_type == 2){ | }else if(task_type == 2){ | ||||
return "原始数据集-图片"; | |||||
return "图片"; | |||||
}else if(task_type == 3){ | }else if(task_type == 3){ | ||||
return "原始数据集-CT影像"; | |||||
return "CT影像"; | |||||
}else if(task_type == 4){ | }else if(task_type == 4){ | ||||
return "原始数据集-视频"; | |||||
return "视频"; | |||||
} | } | ||||
return "其它"; | return "其它"; | ||||
} | } | ||||
@@ -508,6 +499,12 @@ function getTaskSataus(task_status,task_status_desc){ | |||||
else if(task_status == -1){ | else if(task_status == -1){ | ||||
return "关联的数据集已经被删除。" | 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){ | 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){ | 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>"; | return "<a onclick=\"personLabel(\'" + id + "\'," + task_type + ")\"><b>" + getLabelDesc(task_flow_type) + "标注</b></a><br>"; | ||||
}else{ | }else{ | ||||
return ""; | return ""; | ||||
@@ -570,8 +567,59 @@ function display_list(){ | |||||
$('#label_task_list tr').find('td:eq(1)').hide(); | $('#label_task_list tr').find('td:eq(1)').hide(); | ||||
$('#label_task_list tr').find('th: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){//从审核转回标注,标注人不变。 | function startToLabel(taskid, task_type){//从审核转回标注,标注人不变。 | ||||
$.ajax({ | $.ajax({ | ||||
@@ -692,12 +740,12 @@ function setMultiTaskId(){ | |||||
return; | return; | ||||
} | } | ||||
var taskList = []; | 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)); | setTaskId(JSON.stringify(taskList)); | ||||
} | } | ||||
@@ -149,8 +149,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||||
//todo:support other topics | //todo:support other topics | ||||
keyword := strings.Trim(ctx.Query("q"), " ") | 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{ | repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | ||||
ListOptions: models.ListOptions{ | ListOptions: models.ListOptions{ | ||||
@@ -164,7 +163,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||||
OwnerID: opts.OwnerID, | OwnerID: opts.OwnerID, | ||||
AllPublic: true, | AllPublic: true, | ||||
AllLimited: true, | AllLimited: true, | ||||
TopicOnly: topicOnly, | |||||
TopicName: topic, | |||||
IncludeDescription: setting.UI.SearchRepoDescription, | IncludeDescription: setting.UI.SearchRepoDescription, | ||||
}) | }) | ||||
if err != nil { | 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) | repo.Active = int64(repo.NumIssues) + int64(repo.NumPulls) + int64(repo.NumCommit) | ||||
} | } | ||||
ctx.Data["Keyword"] = keyword | ctx.Data["Keyword"] = keyword | ||||
ctx.Data["Topic"] = topic | |||||
ctx.Data["Total"] = count | ctx.Data["Total"] = count | ||||
ctx.Data["Repos"] = repos | ctx.Data["Repos"] = repos | ||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | ||||
@@ -42,7 +42,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
m.Post("/manager/shutdown", Shutdown) | m.Post("/manager/shutdown", Shutdown) | ||||
m.Post("/manager/restart", Restart) | m.Post("/manager/restart", Restart) | ||||
m.Post("/manager/flush-queues", bind(private.FlushOptions{}), FlushQueues) | 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) | }, CheckInternalToken) | ||||
} | } |
@@ -5,11 +5,13 @@ | |||||
package private | package private | ||||
import ( | import ( | ||||
"gitea.com/macaron/macaron" | |||||
"net/http" | "net/http" | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"code.gitea.io/gitea/routers/repo" | |||||
"gitea.com/macaron/macaron" | |||||
) | ) | ||||
func UpdateAllRepoCommitCnt(ctx *macaron.Context) { | func UpdateAllRepoCommitCnt(ctx *macaron.Context) { | ||||
@@ -35,3 +37,8 @@ func UpdateAllRepoCommitCnt(ctx *macaron.Context) { | |||||
"error_msg": "", | "error_msg": "", | ||||
}) | }) | ||||
} | } | ||||
func RepoStatisticManually(ctx *macaron.Context) { | |||||
date := ctx.Query("date") | |||||
repo.RepoStatisticDaily(date) | |||||
} |
@@ -4,6 +4,7 @@ import ( | |||||
"bufio" | "bufio" | ||||
"encoding/json" | "encoding/json" | ||||
"errors" | "errors" | ||||
"fmt" | |||||
"io" | "io" | ||||
"net/http" | "net/http" | ||||
"os" | "os" | ||||
@@ -13,6 +14,8 @@ import ( | |||||
"strings" | "strings" | ||||
"time" | "time" | ||||
"code.gitea.io/gitea/modules/modelarts" | |||||
"code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
"code.gitea.io/gitea/modules/storage" | "code.gitea.io/gitea/modules/storage" | ||||
@@ -67,11 +70,13 @@ func CloudBrainIndex(ctx *context.Context) { | |||||
timestamp := time.Now().Unix() | timestamp := time.Now().Unix() | ||||
for i, task := range ciTasks { | 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 | ciTasks[i].CanDebug = true | ||||
} else { | } else { | ||||
ciTasks[i].CanDebug = false | ciTasks[i].CanDebug = false | ||||
} | } | ||||
ciTasks[i].CanDel = models.CanDelJob(ctx.IsSigned, ctx.User, task) | |||||
} | } | ||||
pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) | 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") | 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) { | func CloudBrainDel(ctx *context.Context) { | ||||
var jobID = ctx.Params(":jobid") | var jobID = ctx.Params(":jobid") | ||||
task, err := models.GetCloudbrainByJobID(jobID) | task, err := models.GetCloudbrainByJobID(jobID) | ||||
@@ -57,6 +57,8 @@ func ModelArtsIndex(ctx *context.Context) { | |||||
} else { | } else { | ||||
ciTasks[i].CanDebug = false | ciTasks[i].CanDebug = false | ||||
} | } | ||||
ciTasks[i].CanDel = models.CanDelJob(ctx.IsSigned, ctx.User, task) | |||||
} | } | ||||
pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) | pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) | ||||
@@ -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 | |||||
} |
@@ -440,6 +440,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { | |||||
return | return | ||||
} | } | ||||
log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) | 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.Flash.Success(ctx.Tr("repo.settings.deletion_success")) | ||||
ctx.Redirect(ctx.Repo.Owner.DashboardLink()) | ctx.Redirect(ctx.Repo.Owner.DashboardLink()) | ||||
@@ -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) | |||||
} |
@@ -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["Paths"] = paths | ||||
ctx.Data["TreeLink"] = treeLink | ctx.Data["TreeLink"] = treeLink | ||||
ctx.Data["TreeNames"] = treeNames | ctx.Data["TreeNames"] = treeNames | ||||
@@ -82,6 +82,20 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) | |||||
return commit.GetTreeEntryByPath(unescapedTarget) | 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) { | func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) { | ||||
wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath()) | wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath()) | ||||
if err != nil { | if err != nil { | ||||
@@ -150,6 +164,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { | |||||
if !entry.IsRegular() { | if !entry.IsRegular() { | ||||
continue | continue | ||||
} | } | ||||
wikiName, err := wiki_service.FilenameToName(entry.Name()) | wikiName, err := wiki_service.FilenameToName(entry.Name()) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrWikiInvalidFileName(err) { | if models.IsErrWikiInvalidFileName(err) { | ||||
@@ -7,6 +7,7 @@ package secure | |||||
import ( | import ( | ||||
"net/http" | "net/http" | ||||
"net/mail" | |||||
"strings" | "strings" | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
@@ -63,6 +64,14 @@ func CreateUser(ctx *context.Context, form api.CreateUserOption) { | |||||
// "422": | // "422": | ||||
// "$ref": "#/responses/validationError" | // "$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{ | u := &models.User{ | ||||
Name: form.Username, | Name: form.Username, | ||||
FullName: form.FullName, | FullName: form.FullName, | ||||
@@ -11,6 +11,8 @@ import ( | |||||
"net/http" | "net/http" | ||||
"strings" | "strings" | ||||
"code.gitea.io/gitea/routers/repo" | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/auth" | "code.gitea.io/gitea/modules/auth" | ||||
"code.gitea.io/gitea/modules/auth/oauth2" | "code.gitea.io/gitea/modules/auth/oauth2" | ||||
@@ -1056,6 +1058,7 @@ func SignOut(ctx *context.Context) { | |||||
}) | }) | ||||
} | } | ||||
HandleSignOut(ctx) | HandleSignOut(ctx) | ||||
go repo.StopJobsByUserID(ctx.User.ID) | |||||
ctx.Redirect(setting.AppSubURL + "/") | ctx.Redirect(setting.AppSubURL + "/") | ||||
} | } | ||||
@@ -154,7 +154,7 @@ | |||||
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}} | {{svg "octicon-person" 16}} {{.i18n.Tr "register"}} | ||||
</a> | </a> | ||||
{{end}} | {{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"}} | {{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}} | ||||
</a> | </a> | ||||
</div><!-- end anonymous right menu --> | </div><!-- end anonymous right menu --> | ||||
@@ -154,7 +154,7 @@ | |||||
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}} | {{svg "octicon-person" 16}} {{.i18n.Tr "register"}} | ||||
</a> | </a> | ||||
{{end}} | {{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"}} | {{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}} | ||||
</a> | </a> | ||||
</div><!-- end anonymous right menu --> | </div><!-- end anonymous right menu --> | ||||
@@ -1,12 +1,12 @@ | |||||
<div class="repos--seach"> | <div class="repos--seach"> | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<div class="ui two column centered grid"> | <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"> | <div class="ui fluid action input"> | ||||
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | ||||
<input type="hidden" name="tab" value="{{$.TabName}}"> | <input type="hidden" name="tab" value="{{$.TabName}}"> | ||||
<input type="hidden" name="sort" value="{{$.SortType}}"> | <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> | </div> | ||||
</form> | </form> | ||||
</div> | </div> | ||||
@@ -6,67 +6,67 @@ | |||||
</svg> | </svg> | ||||
全部领域 | 全部领域 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
大模型 | 大模型 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
AI开发工具 | AI开发工具 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
计算机视觉 | 计算机视觉 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
自然语言处理 | 自然语言处理 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
机器学习 | 机器学习 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
神经网络 | 神经网络 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
自动驾驶 | 自动驾驶 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
机器人 | 机器人 | ||||
</a> | </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"> | <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" /> | <path fill="currentColor" d="M3 11H11V3H3M5 5H9V9H5M13 21H21V13H13M15 15H19V19H15M3 21H11V13H3M5 15H9V19H5M13 3V11H21V3M19 9H15V5H19Z" /> | ||||
</svg> | </svg> | ||||
联邦学习 | 联邦学习 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
数据挖掘 | 数据挖掘 | ||||
</a> | </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"> | <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" /> | <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> | </svg> | ||||
@@ -40,20 +40,20 @@ | |||||
<div class="ui secondary pointing tabular top attached borderless menu navbar"> | <div class="ui secondary pointing tabular top attached borderless menu navbar"> | ||||
{{if .PageIsExplore}} | {{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"> | <svg class="svg octicon-repo" width="16" height="16" aria-hidden="true"> | ||||
<use xlink:href="#octicon-repo" /> | <use xlink:href="#octicon-repo" /> | ||||
</svg> | </svg> | ||||
热门{{.i18n.Tr "explore.repos"}} | 热门{{.i18n.Tr "explore.repos"}} | ||||
</a> | </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"> | <svg class="svg octicon-inbox" width="16" height="16" aria-hidden="true"> | ||||
<use xlink:href="#octicon-inbox" /> | <use xlink:href="#octicon-inbox" /> | ||||
</svg> | </svg> | ||||
活跃{{.i18n.Tr "explore.repos"}} | 活跃{{.i18n.Tr "explore.repos"}} | ||||
</a> | </a> | ||||
{{end}} | {{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"> | <svg class="svg octicon-organization" width="16" height="16" aria-hidden="true"> | ||||
<use xlink:href="#octicon-organization" /> | <use xlink:href="#octicon-organization" /> | ||||
</svg> {{.i18n.Tr "repo.issues.filter_sort.recentupdate"}} | </svg> {{.i18n.Tr "repo.issues.filter_sort.recentupdate"}} | ||||
@@ -67,16 +67,16 @@ | |||||
<i class="dropdown icon"></i> | <i class="dropdown icon"></i> | ||||
</span> | </span> | ||||
<div class="menu"> | <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> | </div> | ||||
</div> | </div> | ||||
@@ -143,7 +143,7 @@ | |||||
{{if .Topics }} | {{if .Topics }} | ||||
<div class="ui tags"> | <div class="ui tags"> | ||||
{{range .Topics}} | {{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}} | {{end}} | ||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
@@ -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"> | <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"> | <div class="ui fluid action input"> | ||||
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | ||||
<input type="hidden" name="tab" value="{{$.TabName}}"> | |||||
<input type="hidden" name="topic" value="{{$.Topic}}"> | |||||
<input type="hidden" name="sort" value="{{$.SortType}}"> | <input type="hidden" name="sort" value="{{$.SortType}}"> | ||||
<button class="ui green button">{{.i18n.Tr "explore.search"}}</button> | <button class="ui green button">{{.i18n.Tr "explore.search"}}</button> | ||||
</div> | </div> | ||||
@@ -1,11 +1,11 @@ | |||||
<div class="repos--seach"> | <div class="repos--seach"> | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<div class="ui two column centered grid"> | <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"> | <div class="ui fluid action input"> | ||||
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | ||||
<input type="hidden" name="tab" value="{{$.TabName}}"> | <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> | </div> | ||||
</form> | </form> | ||||
</div> | </div> | ||||
@@ -58,7 +58,7 @@ | |||||
{{if .Topics }} | {{if .Topics }} | ||||
<div class="ui tags"> | <div class="ui tags"> | ||||
{{range .Topics}} | {{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}} | {{end}} | ||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
@@ -187,6 +187,12 @@ | |||||
cursor: pointer; | cursor: pointer; | ||||
pointer-events: none; | pointer-events: none; | ||||
} | } | ||||
.time-show{ | |||||
font-size: 10px; | |||||
margin-top: 0.4rem; | |||||
display: inline-block; | |||||
} | |||||
</style> | </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 sixteen wide column"> | |||||
<div class="ui two column stackable grid"> | <div class="ui two column stackable grid"> | ||||
<div class="column"> | <div class="column"> | ||||
</div> | </div> | ||||
<!-- <div class="column right aligned"> | |||||
<div class="column right aligned"> | |||||
<div class="ui right dropdown type jump item"> | <div class="ui right dropdown type jump item"> | ||||
<span class="text"> | <span class="text"> | ||||
{{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i> | {{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i> | ||||
</span> | </span> | ||||
</div> | </div> | ||||
</div> --> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | |||||
</div> --> | |||||
<!-- 任务展示 --> | <!-- 任务展示 --> | ||||
<!-- 表头 --> | |||||
<div class="dataset list"> | <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}} | {{range .Tasks}} | ||||
<div class="ui grid stackable item"> | <div class="ui grid stackable item"> | ||||
<div class="row"> | <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> | </a> | ||||
</div> | </div> | ||||
<div class="three wide column"> | <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> | ||||
<!-- <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> | ||||
<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="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")}} | {{if and (ne .Status "WAITING") (ne .JobType "DEBUG")}} | ||||
<a class="ui basic button" href="{{$.Link}}/{{.JobID}}/rate" target="_blank"> | <a class="ui basic button" href="{{$.Link}}/{{.JobID}}/rate" target="_blank"> | ||||
评分 | 评分 | ||||
@@ -304,10 +356,10 @@ | |||||
</div> | </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}} | {{$.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> | </a> | ||||
</form> | </form> | ||||
@@ -436,15 +488,18 @@ | |||||
$(document).ready(loadJobStatus); | $(document).ready(loadJobStatus); | ||||
function loadJobStatus() { | function loadJobStatus() { | ||||
$(".job-status").each((index, job) => { | $(".job-status").each((index, job) => { | ||||
console.log("---------",index,job) | |||||
const jobID = job.dataset.jobid; | const jobID = job.dataset.jobid; | ||||
const repoPath = job.dataset.repopath; | const repoPath = job.dataset.repopath; | ||||
if (job.textContent.trim() == 'STOPPED') { | if (job.textContent.trim() == 'STOPPED') { | ||||
return | return | ||||
} | } | ||||
$.get(`/api/v1/repos/${repoPath}/cloudbrain/${jobID}`, (data) => { | $.get(`/api/v1/repos/${repoPath}/cloudbrain/${jobID}`, (data) => { | ||||
const jobID = data.JobID | const jobID = data.JobID | ||||
const status = data.JobStatus | const status = data.JobStatus | ||||
console.log("status",status) | |||||
if (status != job.textContent.trim()) { | if (status != job.textContent.trim()) { | ||||
//$('#' + jobID).text(status) | //$('#' + jobID).text(status) | ||||
//if (status == 'STOPPED') { | //if (status == 'STOPPED') { | ||||
@@ -1,63 +1,52 @@ | |||||
<style> | |||||
</style> | |||||
{{if .Attachments}} | {{if .Attachments}} | ||||
{{range .Attachments}} | {{range .Attachments}} | ||||
<div class="ui grid item" id="{{.UUID}}"> | <div class="ui grid item" id="{{.UUID}}"> | ||||
<div class="row"> | <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}}"> | <a class="title" href="{{.DownloadURL}}?type={{$.Type}}"> | ||||
<span class="fitted">{{svg "octicon-cloud-download" 16}}</span> {{.Name}} | |||||
{{svg "octicon-cloud-download" 16}} {{.Name}} | |||||
</a> | </a> | ||||
</div> | </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> | </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> | </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}} | {{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> | <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> | ||||
</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}} | {{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}} | ||||
{{end}} | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
@@ -37,7 +37,7 @@ | |||||
</div> | </div> | ||||
{{if .Permission.CanWrite $.UnitTypeDatasets}} | {{if .Permission.CanWrite $.UnitTypeDatasets}} | ||||
<div class="column four wide right aligned"> | <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"}} | {{.i18n.Tr "dataset.edit"}} | ||||
</a> | </a> | ||||
</div> | </div> | ||||
@@ -66,7 +66,7 @@ | |||||
<input name="type" value="{{.Type}}" type="hidden" /> | <input name="type" value="{{.Type}}" type="hidden" /> | ||||
<div class="sixteen wide column"> | <div class="sixteen wide column"> | ||||
<a class="ui button" id="cancel">{{.i18n.Tr "cancel"}}</a> | <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> | ||||
</div> | </div> | ||||
@@ -80,7 +80,7 @@ | |||||
<div class="ui sixteen wide column"> | <div class="ui sixteen wide column"> | ||||
<div class="ui two column stackable grid"> | <div class="ui two column stackable grid"> | ||||
<div class="column"> | <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> | ||||
<div class="column right aligned" style="z-index:1"> | <div class="column right aligned" style="z-index:1"> | ||||
<div class="ui right dropdown type jump item"> | <div class="ui right dropdown type jump item"> | ||||
@@ -17,9 +17,11 @@ | |||||
<div class="repository dataset dir-list view"> | <div class="repository dataset dir-list view"> | ||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<input type="hidden" id="repoId" value="{{.repoId}}"> | <input type="hidden" id="repoId" value="{{.repoId}}"> | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<div class="header"> | <div class="header"> | ||||
<h3 class="modal-title">人工标注任务列表</h3> | |||||
<h3 class="modal-title">标注任务列表</h3> | |||||
</div> | </div> | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<div class="ui container"> | <div class="ui container"> | ||||
@@ -45,8 +47,8 @@ | |||||
</div> | </div> | ||||
<div class="ui container" style="height: 30px;"> | <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="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> | <button type="button" onclick="countLabel();" class="ui blue button" style="float:right;margin-left:20px;">统计所有标注数量</button> | ||||
@@ -65,7 +67,11 @@ | |||||
<div class="field"> | <div class="field"> | ||||
<label for="exampleInputPassword1">选择数据集对象<font color=red>*</font></label> | <label for="exampleInputPassword1">选择数据集对象<font color=red>*</font></label> | ||||
<select name="pre_predict_task" id="dataset_list" onchange="dataset_sele_Change(this)"> | <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> | </select> | ||||
</div> | </div> | ||||
@@ -105,44 +111,50 @@ | |||||
<div id="labelModal" class="ui predict modal"> | <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> | </div> | ||||
</div> <!-- /.box-body --> | </div> <!-- /.box-body --> | ||||
@@ -55,14 +55,48 @@ | |||||
#contributorInfo > a.circular:nth-child(9n+8){ | #contributorInfo > a.circular:nth-child(9n+8){ | ||||
background-color: #bfd0aa; | 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> | </style> | ||||
<div class="repository file list"> | <div class="repository file list"> | ||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <div class="ui container"> | ||||
{{template "base/alert" .}} | {{template "base/alert" .}} | ||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}} | {{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="fourteen wide column"> | ||||
<div class="field"> | <div class="field"> | ||||
<div class="ui fluid multiple search selection dropdown"> | <div class="ui fluid multiple search selection dropdown"> | ||||
@@ -78,13 +112,14 @@ | |||||
<a class="ui button primary" href="javascript:;" id="save_topic" | <a class="ui button primary" href="javascript:;" id="save_topic" | ||||
data-link="{{.RepoLink}}/topics">{{.i18n.Tr "repo.topic.done"}}</a> | data-link="{{.RepoLink}}/topics">{{.i18n.Tr "repo.topic.done"}}</a> | ||||
</div> | </div> | ||||
</div> | |||||
</div> --> | |||||
{{end}} | {{end}} | ||||
<div class="hide" id="validate_prompt"> | <div class="hide" id="validate_prompt"> | ||||
<span id="count_prompt">{{.i18n.Tr "repo.topic.count_prompt"}}</span> | <span id="count_prompt">{{.i18n.Tr "repo.topic.count_prompt"}}</span> | ||||
<span id="format_prompt">{{.i18n.Tr "repo.topic.format_prompt"}}</span> | <span id="format_prompt">{{.i18n.Tr "repo.topic.format_prompt"}}</span> | ||||
</div> | </div> | ||||
<div class="ui repo-description stackable grid"> | |||||
<div class="ui repo-description stackable grid"> | |||||
{{if .RepoSearchEnabled}} | {{if .RepoSearchEnabled}} | ||||
<div class="ui repo-search four wide column"> | <div class="ui repo-search four wide column"> | ||||
@@ -101,7 +136,7 @@ | |||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
{{if .Repository.IsArchived}} | {{if .Repository.IsArchived}} | ||||
<div class="ui warning message"> | <div class="ui warning message"> | ||||
{{.i18n.Tr "repo.archive.title"}} | {{.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}}"> | <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> | <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> | </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> | </div> | ||||
{{end}} | {{end}} | ||||
{{else}} | {{else}} | ||||
@@ -204,7 +254,7 @@ | |||||
{{template "repo/view_list" .}} | {{template "repo/view_list" .}} | ||||
{{end}} | {{end}} | ||||
</div> | </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"> | <div id="repo-desc"> | ||||
<h4 id="about-desc" class="ui header">简介 | <h4 id="about-desc" class="ui header">简介 | ||||
<!-- <a class="edit-icon" href="javascript:void(0)"> | <!-- <a class="edit-icon" href="javascript:void(0)"> | ||||
@@ -217,28 +267,47 @@ | |||||
{{else}} | {{else}} | ||||
<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span> | <span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span> | ||||
{{end}} | {{end}} | ||||
</p> | </p> | ||||
</div> | </div> | ||||
{{if .Repository.Website}} | {{if .Repository.Website}} | ||||
<p class="ui"> | <p class="ui"> | ||||
<i class="gray linkify icon"></i> | <i class="gray linkify icon"></i> | ||||
<a class="link edit-link" target="_blank" title="{{.Repository.Website}}" href="{{.Repository.Website}}">{{.Repository.Website}}</a> | <a class="link edit-link" target="_blank" title="{{.Repository.Website}}" href="{{.Repository.Website}}">{{.Repository.Website}}</a> | ||||
</p> | </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"> | <p class="ui"> | ||||
<i class="grey code icon"></i> | <i class="grey code icon"></i> | ||||
{{range .LanguageStats}} | {{range .LanguageStats}} | ||||
@@ -246,14 +315,14 @@ | |||||
{{end}} | {{end}} | ||||
</p> | </p> | ||||
{{if .LICENSE}} | {{if .LICENSE}} | ||||
<p class="ui"> | <p class="ui"> | ||||
<i class="grey clone icon"></i> | <i class="grey clone icon"></i> | ||||
{{.LICENSE}} | {{.LICENSE}} | ||||
</p> | </p> | ||||
{{end}} | {{end}} | ||||
<div class="ui divider"></div> | <div class="ui divider"></div> | ||||
@@ -269,22 +338,22 @@ | |||||
{{range .ContributorInfo}} | {{range .ContributorInfo}} | ||||
{{if .UserInfo}} | {{if .UserInfo}} | ||||
<a href="{{AppSubUrl}}/{{.UserInfo.Name}}"><img class="ui avatar image" src="{{.UserInfo.RelAvatarLink}}"></a> | <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> | <a href="mailto:{{.Email}}" class="circular ui button">{{.Email}}</a> | ||||
{{end}} | {{end}} | ||||
{{end}} | {{end}} | ||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<script type="text/javascript"> | <script type="text/javascript"> | ||||
$(document).ready(function(){ | |||||
$(document).ready(function(){ | |||||
$(".membersmore").click(function(){ | $(".membersmore").click(function(){ | ||||
$("#contributorInfo > a:nth-child(n+25)").show(); | $("#contributorInfo > a:nth-child(n+25)").show(); | ||||
}); | }); | ||||
@@ -2,14 +2,24 @@ | |||||
<div class="repository labels"> | <div class="repository labels"> | ||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <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> | <div class="ui divider"></div> | ||||
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}} | {{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}} | ||||
{{template "repo/issue/labels/label_new" .}} | {{template "repo/issue/labels/label_new" .}} | ||||
@@ -3,14 +3,14 @@ | |||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<div class="ui three column stackable grid"> | <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> | ||||
<div class="column center aligned"> | <div class="column center aligned"> | ||||
{{template "repo/issue/search" .}} | {{template "repo/issue/search" .}} | ||||
</div> | </div> | ||||
{{if not .Repository.IsArchived}} | {{if not .Repository.IsArchived}} | ||||
<div class="column right aligned"> | <div class="column right aligned"> | ||||
{{template "repo/issue/navbar" .}} | |||||
{{if .PageIsIssueList}} | {{if .PageIsIssueList}} | ||||
<a class="ui green button" href="{{.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a> | <a class="ui green button" href="{{.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a> | ||||
{{else}} | {{else}} | ||||
@@ -3,8 +3,14 @@ | |||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<div class="ui three column stackable grid"> | <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> | ||||
<div class="column center aligned"> | <div class="column center aligned"> | ||||
@@ -2,13 +2,29 @@ | |||||
<div class="repository new milestone"> | <div class="repository new milestone"> | ||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <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> | </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> | ||||
<div class="ui divider"></div> | <div class="ui divider"></div> | ||||
<h2 class="ui dividing header"> | <h2 class="ui dividing header"> | ||||
@@ -2,14 +2,25 @@ | |||||
<div class="repository milestones"> | <div class="repository milestones"> | ||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <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> | <div class="ui divider"></div> | ||||
{{template "base/alert" .}} | {{template "base/alert" .}} | ||||
<div class="ui tiny basic buttons"> | <div class="ui tiny basic buttons"> | ||||
@@ -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 .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> | <a class="{{if .PageIsMilestones}}active{{end}} item" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a> | ||||
</div> | </div> |
@@ -2,9 +2,21 @@ | |||||
<div class="repository new issue"> | <div class="repository new issue"> | ||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <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> | <div class="ui divider"></div> | ||||
{{template "repo/issue/new_form" .}} | {{template "repo/issue/new_form" .}} | ||||
</div> | </div> | ||||
@@ -7,7 +7,7 @@ | |||||
<input type="hidden" name="assignee" value="{{$.AssigneeID}}"/> | <input type="hidden" name="assignee" value="{{$.AssigneeID}}"/> | ||||
<div class="ui search action input"> | <div class="ui search action input"> | ||||
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | <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> | ||||
</div> | </div> | ||||
</form> | </form> |
@@ -3,11 +3,24 @@ | |||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<div class="ui two column stackable grid"> | <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> | </div> | ||||
{{if not .Repository.IsArchived}} | {{if not .Repository.IsArchived}} | ||||
<div class="column right aligned"> | <div class="column right aligned"> | ||||
{{template "repo/issue/navbar" .}} | |||||
{{if .PageIsIssueList}} | {{if .PageIsIssueList}} | ||||
<a class="ui green button" href="{{.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a> | <a class="ui green button" href="{{.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a> | ||||
{{else}} | {{else}} | ||||
@@ -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 sixteen wide column"> | |||||
<div class="ui two column stackable grid"> | <div class="ui two column stackable grid"> | ||||
<div class="column"> | <div class="column"> | ||||
</div> | </div> | ||||
<!-- <div class="column right aligned"> | |||||
<div class="column right aligned"> | |||||
<div class="ui right dropdown type jump item"> | <div class="ui right dropdown type jump item"> | ||||
<span class="text"> | <span class="text"> | ||||
{{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i> | {{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i> | ||||
</span> | </span> | ||||
</div> | </div> | ||||
</div> --> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | |||||
</div> --> | |||||
<!-- 任务展示 --> | <!-- 任务展示 --> | ||||
<div class="dataset list"> | <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}} | {{range .Tasks}} | ||||
<div class="ui grid stackable item"> | <div class="ui grid stackable item"> | ||||
<div class="row"> | <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> | </a> | ||||
</div> | </div> | ||||
<div class="three wide column"> | <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}} | {{.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> | ||||
<!-- 任务创建时间 --> | <!-- 任务创建时间 --> | ||||
<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> | ||||
<div class="seven wide column text right"> | <div class="seven wide column text right"> | ||||
@@ -270,7 +304,7 @@ | |||||
<a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}"> | <a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}"> | ||||
查看 | 查看 | ||||
</a> | </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> | </a> | ||||
<form id="stopForm-{{.JobID}}" action="{{if ne .Status "RUNNING"}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/stop{{end}}" method="post" style="margin-left:-1px;"> | <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> | </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}} | {{$.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> | </a> | ||||
</form> | </form> | ||||
@@ -2,11 +2,25 @@ | |||||
<div class="repository view issue pull commits"> | <div class="repository view issue pull commits"> | ||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<div class="navbar"> | |||||
<!-- <div class="navbar"> | |||||
{{template "repo/issue/navbar" .}} | {{template "repo/issue/navbar" .}} | ||||
<div class="ui right"> | <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> | <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> --> | |||||
<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> | ||||
<div class="ui divider"></div> | <div class="ui divider"></div> | ||||
{{template "repo/issue/view_title" .}} | {{template "repo/issue/view_title" .}} | ||||
@@ -2,11 +2,25 @@ | |||||
<div class="repository view issue pull files diff"> | <div class="repository view issue pull files diff"> | ||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}"> | <div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}"> | ||||
<div class="navbar"> | |||||
<!-- <div class="navbar"> | |||||
{{template "repo/issue/navbar" .}} | {{template "repo/issue/navbar" .}} | ||||
<div class="ui right"> | <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> | <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> --> | |||||
<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> | ||||
<div class="ui divider"></div> | <div class="ui divider"></div> | ||||
{{template "repo/issue/view_title" .}} | {{template "repo/issue/view_title" .}} | ||||
@@ -74,7 +74,7 @@ | |||||
<input type="hidden" name="state" value="{{$.State}}"/> | <input type="hidden" name="state" value="{{$.State}}"/> | ||||
<div class="ui search action input"> | <div class="ui search action input"> | ||||
<input name="q" value="{{$.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> | <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> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
@@ -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> |
@@ -1,17 +1,16 @@ | |||||
<template> | <template> | ||||
<div class="dropzone-wrapper dataset-files"> | <div class="dropzone-wrapper dataset-files"> | ||||
<div class="ui pointing below red basic label"> | |||||
<i class="icon info circle"></i>只有zip格式的数据集才能发起云脑任务 | |||||
</div> | |||||
<div | <div | ||||
id="dataset" | id="dataset" | ||||
class="dropzone" | class="dropzone" | ||||
/> | /> | ||||
<p class="upload-info"> | <p class="upload-info"> | ||||
{{ file_status_text }} | {{ file_status_text }} | ||||
<span class="success">{{ status }}</span> | |||||
<strong class="success text red">{{ status }}</strong> | |||||
</p> | </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> | </div> | ||||
</template> | </template> | ||||
@@ -6,9 +6,12 @@ | |||||
/> | /> | ||||
<p class="upload-info"> | <p class="upload-info"> | ||||
{{ file_status_text }} | {{ 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> | ||||
<p>云脑1提供 <span class="text blue">CPU / GPU</span> 资源,云脑2提供 <span class="text blue">Ascend NPU</span> 资源;调试使用的数据集也需要上传到对应的环境。</p> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
@@ -36,6 +36,7 @@ import MinioUploader from './components/MinioUploader.vue'; | |||||
import ObsUploader from './components/ObsUploader.vue'; | import ObsUploader from './components/ObsUploader.vue'; | ||||
import EditAboutInfo from './components/EditAboutInfo.vue'; | import EditAboutInfo from './components/EditAboutInfo.vue'; | ||||
import Images from './components/Images.vue' | import Images from './components/Images.vue' | ||||
import EditTopics from './components/EditTopics.vue' | |||||
Vue.use(ElementUI); | Vue.use(ElementUI); | ||||
Vue.prototype.$axios = axios; | Vue.prototype.$axios = axios; | ||||
@@ -2967,11 +2968,13 @@ $(document).ready(async () => { | |||||
initVueUploader(); | initVueUploader(); | ||||
initObsUploader(); | initObsUploader(); | ||||
initVueEditAbout(); | initVueEditAbout(); | ||||
initVueEditTopic(); | |||||
initVueImages(); | initVueImages(); | ||||
initTeamSettings(); | initTeamSettings(); | ||||
initCtrlEnterSubmit(); | initCtrlEnterSubmit(); | ||||
initNavbarContentToggle(); | initNavbarContentToggle(); | ||||
initTopicbar(); | |||||
// initTopicbar(); | |||||
// closeTopicbar(); | |||||
initU2FAuth(); | initU2FAuth(); | ||||
initU2FRegister(); | initU2FRegister(); | ||||
initIssueList(); | 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() { | function initVueImages() { | ||||
const el = document.getElementById('images'); | const el = document.getElementById('images'); | ||||
console.log("el",el) | console.log("el",el) | ||||
@@ -3677,6 +3692,7 @@ function initVueImages() { | |||||
new Vue({ | new Vue({ | ||||
el: '#images', | el: '#images', | ||||
render: h => h(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 () { | window.toggleDeadlineForm = function () { | ||||
$('#deadlineForm').fadeToggle(150); | $('#deadlineForm').fadeToggle(150); | ||||
@@ -140,19 +140,32 @@ | |||||
border: 1px solid #ffffff; | border: 1px solid #ffffff; | ||||
} | } | ||||
} | } | ||||
} | |||||
} | |||||
} | } | ||||
.item { | .item { | ||||
border-bottom: 1px solid rgba(34,36,38,.15); | border-bottom: 1px solid rgba(34,36,38,.15); | ||||
.ui.buttons { | |||||
.button { | |||||
box-shadow: none !important; | |||||
} | |||||
} | |||||
} | } | ||||
.ui.grid > .row { | .ui.grid > .row { | ||||
align-items: center; | align-items: center; | ||||
} | } | ||||
.title { | .title { | ||||
font-size: 16px; | font-size: 16px; | ||||
font-weight: bold; | font-weight: bold; | ||||
margin: 0 6px; | |||||
margin: 0 6px; | |||||
overflow: hidden; | |||||
padding-right: 15px; | |||||
white-space: nowrap; | |||||
text-overflow: ellipsis; | |||||
display: block; | |||||
} | } | ||||
.directory-seperator { | .directory-seperator { | ||||
padding: 0 4px; | padding: 0 4px; | ||||
@@ -220,3 +220,25 @@ footer .column{margin-bottom:0!important; padding-bottom:0!important;} | |||||
.ui.vertical.menu .dropdown.item .menu { | .ui.vertical.menu .dropdown.item .menu { | ||||
left: 50%; | 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%; | |||||
} | |||||