@@ -182,6 +182,7 @@ func CreateOrganization(org, owner *User) (err error) { | |||||
if _, err = sess.Insert(&OrgUser{ | if _, err = sess.Insert(&OrgUser{ | ||||
UID: owner.ID, | UID: owner.ID, | ||||
OrgID: org.ID, | OrgID: org.ID, | ||||
IsPublic: setting.Service.DefaultOrgMemberVisible, | |||||
}); err != nil { | }); err != nil { | ||||
return fmt.Errorf("insert org-user relation: %v", err) | return fmt.Errorf("insert org-user relation: %v", err) | ||||
} | } | ||||
@@ -210,9 +210,12 @@ type Repository struct { | |||||
Balance string `xorm:"NOT NULL DEFAULT '0'"` | Balance string `xorm:"NOT NULL DEFAULT '0'"` | ||||
BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"` | BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"` | ||||
// git clone total count | |||||
// git clone and git pull total count | |||||
CloneCnt int64 `xorm:"NOT NULL DEFAULT 0"` | CloneCnt int64 `xorm:"NOT NULL DEFAULT 0"` | ||||
// only git clone total count | |||||
GitCloneCnt int64 `xorm:"NOT NULL DEFAULT 0"` | |||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | ||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | ||||
@@ -2473,6 +2476,24 @@ func (repo *Repository) IncreaseCloneCnt() { | |||||
return | return | ||||
} | } | ||||
func (repo *Repository) IncreaseGitCloneCnt() { | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
if err := sess.Begin(); err != nil { | |||||
return | |||||
} | |||||
if _, err := sess.Exec("UPDATE `repository` SET git_clone_cnt = git_clone_cnt + 1 WHERE id = ?", repo.ID); err != nil { | |||||
return | |||||
} | |||||
if err := sess.Commit(); err != nil { | |||||
return | |||||
} | |||||
return | |||||
} | |||||
func UpdateRepositoryCommitNum(repo *Repository) error { | func UpdateRepositoryCommitNum(repo *Repository) error { | ||||
if _, err := x.Exec("UPDATE `repository` SET num_commit = ? where id = ?", repo.NumCommit, repo.ID); err != nil { | if _, err := x.Exec("UPDATE `repository` SET num_commit = ? where id = ?", repo.NumCommit, repo.ID); err != nil { | ||||
return err | return err | ||||
@@ -11,7 +11,8 @@ import ( | |||||
type ContributorWithUserId struct { | type ContributorWithUserId struct { | ||||
git.Contributor | git.Contributor | ||||
UserId int64 | |||||
UserId int64 | |||||
IsAdmin bool | |||||
} | } | ||||
func GetRepoKPIStats(repo *Repository) (*git.RepoKPIStats, error) { | func GetRepoKPIStats(repo *Repository) (*git.RepoKPIStats, error) { | ||||
@@ -144,6 +145,7 @@ func GetTop10Contributor(repoPath string) ([]ContributorWithUserId, error) { | |||||
contributorDistinctDict[user.Email] = ContributorWithUserId{ | contributorDistinctDict[user.Email] = ContributorWithUserId{ | ||||
contributor, | contributor, | ||||
user.ID, | user.ID, | ||||
user.IsAdmin, | |||||
} | } | ||||
} else { | } else { | ||||
@@ -156,6 +158,7 @@ func GetTop10Contributor(repoPath string) ([]ContributorWithUserId, error) { | |||||
contributorDistinctDict[contributor.Email] = ContributorWithUserId{ | contributorDistinctDict[contributor.Email] = ContributorWithUserId{ | ||||
contributor, | contributor, | ||||
-1, | -1, | ||||
false, | |||||
} | } | ||||
} else { | } else { | ||||
value.CommitCnt += contributor.CommitCnt | value.CommitCnt += contributor.CommitCnt | ||||
@@ -158,6 +158,13 @@ func InsertRepoStat(repoStat *RepoStatistic) (int64, error) { | |||||
return xStatistic.Insert(repoStat) | return xStatistic.Insert(repoStat) | ||||
} | } | ||||
func RestoreRepoStatFork(numForks int64, repoId int64) error { | |||||
sql := "update repo_statistic set num_forks=? where repo_id=?" | |||||
_, err := xStatistic.Exec(sql, numForks, repoId) | |||||
return err | |||||
} | |||||
func UpdateRepoStat(repoStat *RepoStatistic) error { | func UpdateRepoStat(repoStat *RepoStatistic) error { | ||||
sql := "update repo_statistic set impact=?,completeness=?,liveness=?,project_health=?,team_health=?,growth=?,radar_total=? where repo_id=? and date=?" | sql := "update repo_statistic set impact=?,completeness=?,liveness=?,project_health=?,team_health=?,growth=?,radar_total=? where repo_id=? and date=?" | ||||
@@ -11,6 +11,7 @@ import ( | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/migrations" | "code.gitea.io/gitea/modules/migrations" | ||||
repository_service "code.gitea.io/gitea/modules/repository" | repository_service "code.gitea.io/gitea/modules/repository" | ||||
api_repo "code.gitea.io/gitea/routers/api/v1/repo" | |||||
"code.gitea.io/gitea/routers/repo" | "code.gitea.io/gitea/routers/repo" | ||||
mirror_service "code.gitea.io/gitea/services/mirror" | mirror_service "code.gitea.io/gitea/services/mirror" | ||||
) | ) | ||||
@@ -195,6 +196,17 @@ func registerHandleUserStatistic() { | |||||
}) | }) | ||||
} | } | ||||
func registerHandleClearRepoStatisticFile() { | |||||
RegisterTaskFatal("handle_repo_clear_statistic_file", &BaseConfig{ | |||||
Enabled: true, | |||||
RunAtStart: false, | |||||
Schedule: "@daily", | |||||
}, func(ctx context.Context, _ *models.User, _ Config) error { | |||||
api_repo.ClearUnusedStatisticsFile() | |||||
return nil | |||||
}) | |||||
} | |||||
func initBasicTasks() { | func initBasicTasks() { | ||||
registerUpdateMirrorTask() | registerUpdateMirrorTask() | ||||
registerRepoHealthCheck() | registerRepoHealthCheck() | ||||
@@ -163,6 +163,7 @@ var ( | |||||
// UI settings | // UI settings | ||||
UI = struct { | UI = struct { | ||||
ExplorePagingNum int | ExplorePagingNum int | ||||
ContributorPagingNum int | |||||
IssuePagingNum int | IssuePagingNum int | ||||
RepoSearchPagingNum int | RepoSearchPagingNum int | ||||
MembersPagingNum int | MembersPagingNum int | ||||
@@ -203,19 +204,20 @@ var ( | |||||
Keywords string | Keywords string | ||||
} `ini:"ui.meta"` | } `ini:"ui.meta"` | ||||
}{ | }{ | ||||
ExplorePagingNum: 20, | |||||
IssuePagingNum: 10, | |||||
RepoSearchPagingNum: 10, | |||||
MembersPagingNum: 20, | |||||
FeedMaxCommitNum: 5, | |||||
GraphMaxCommitNum: 100, | |||||
CodeCommentLines: 4, | |||||
ReactionMaxUserNum: 10, | |||||
ThemeColorMetaTag: `#6cc644`, | |||||
MaxDisplayFileSize: 8388608, | |||||
DefaultTheme: `gitea`, | |||||
Themes: []string{`gitea`, `arc-green`}, | |||||
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | |||||
ExplorePagingNum: 20, | |||||
ContributorPagingNum: 50, | |||||
IssuePagingNum: 10, | |||||
RepoSearchPagingNum: 10, | |||||
MembersPagingNum: 20, | |||||
FeedMaxCommitNum: 5, | |||||
GraphMaxCommitNum: 100, | |||||
CodeCommentLines: 4, | |||||
ReactionMaxUserNum: 10, | |||||
ThemeColorMetaTag: `#6cc644`, | |||||
MaxDisplayFileSize: 8388608, | |||||
DefaultTheme: `gitea`, | |||||
Themes: []string{`gitea`, `arc-green`}, | |||||
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | |||||
Notification: struct { | Notification: struct { | ||||
MinTimeout time.Duration | MinTimeout time.Duration | ||||
TimeoutStep time.Duration | TimeoutStep time.Duration | ||||
@@ -545,6 +547,7 @@ var ( | |||||
GrowthCommit float64 | GrowthCommit float64 | ||||
GrowthComments float64 | GrowthComments float64 | ||||
RecordBeginTime string | RecordBeginTime string | ||||
Path string | |||||
}{} | }{} | ||||
) | ) | ||||
@@ -1325,7 +1328,8 @@ func SetRadarMapConfig() { | |||||
RadarMap.GrowthContributors = sec.Key("growth_contributors").MustFloat64(0.2) | RadarMap.GrowthContributors = sec.Key("growth_contributors").MustFloat64(0.2) | ||||
RadarMap.GrowthCommit = sec.Key("growth_commit").MustFloat64(0.2) | RadarMap.GrowthCommit = sec.Key("growth_commit").MustFloat64(0.2) | ||||
RadarMap.GrowthComments = sec.Key("growth_comments").MustFloat64(0.2) | RadarMap.GrowthComments = sec.Key("growth_comments").MustFloat64(0.2) | ||||
RadarMap.RecordBeginTime = sec.Key("record_beigin_time").MustString("2021-11-04") | |||||
RadarMap.RecordBeginTime = sec.Key("record_beigin_time").MustString("2021-11-05") | |||||
RadarMap.Path = sec.Key("PATH").MustString("data/projectborad") | |||||
} | } | ||||
@@ -10,11 +10,11 @@ import ( | |||||
"strconv" | "strconv" | ||||
"strings" | "strings" | ||||
"github.com/unknwon/com" | |||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"code.gitea.io/gitea/modules/obs" | "code.gitea.io/gitea/modules/obs" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"github.com/unknwon/com" | |||||
) | ) | ||||
type FileInfo struct { | type FileInfo struct { | ||||
@@ -178,30 +178,45 @@ func GetObsListObject(jobName, parentDir string) ([]FileInfo, error) { | |||||
input := &obs.ListObjectsInput{} | input := &obs.ListObjectsInput{} | ||||
input.Bucket = setting.Bucket | input.Bucket = setting.Bucket | ||||
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/") | input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/") | ||||
strPrefix := strings.Split(input.Prefix, "/") | |||||
output, err := ObsCli.ListObjects(input) | output, err := ObsCli.ListObjects(input) | ||||
fileInfos := make([]FileInfo, 0) | fileInfos := make([]FileInfo, 0) | ||||
if err == nil { | if err == nil { | ||||
for _, val := range output.Contents { | for _, val := range output.Contents { | ||||
str1 := strings.Split(val.Key, "/") | str1 := strings.Split(val.Key, "/") | ||||
var isDir bool | var isDir bool | ||||
var fileName,nextParentDir string | |||||
var fileName, nextParentDir string | |||||
if strings.HasSuffix(val.Key, "/") { | if strings.HasSuffix(val.Key, "/") { | ||||
//dirs in next level dir | |||||
if len(str1)-len(strPrefix) > 2 { | |||||
continue | |||||
} | |||||
fileName = str1[len(str1)-2] | fileName = str1[len(str1)-2] | ||||
isDir = true | isDir = true | ||||
nextParentDir = fileName | |||||
if fileName == parentDir || (fileName + "/") == setting.OutPutPath { | |||||
if parentDir == "" { | |||||
nextParentDir = fileName | |||||
} else { | |||||
nextParentDir = parentDir + "/" + fileName | |||||
} | |||||
if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == setting.OutPutPath { | |||||
continue | continue | ||||
} | } | ||||
} else { | } else { | ||||
//files in next level dir | |||||
if len(str1)-len(strPrefix) > 1 { | |||||
continue | |||||
} | |||||
fileName = str1[len(str1)-1] | fileName = str1[len(str1)-1] | ||||
isDir = false | isDir = false | ||||
nextParentDir = parentDir | |||||
} | } | ||||
fileInfo := FileInfo{ | fileInfo := FileInfo{ | ||||
ModTime: val.LastModified.Format("2006-01-02 15:04:05"), | |||||
ModTime: val.LastModified.Local().Format("2006-01-02 15:04:05"), | |||||
FileName: fileName, | FileName: fileName, | ||||
Size: val.Size, | |||||
IsDir:isDir, | |||||
Size: val.Size, | |||||
IsDir: isDir, | |||||
ParenDir: nextParentDir, | ParenDir: nextParentDir, | ||||
} | } | ||||
fileInfos = append(fileInfos, fileInfo) | fileInfos = append(fileInfos, fileInfo) | ||||
@@ -242,7 +257,7 @@ func GetObsCreateSignedUrl(jobName, parentDir, fileName string) (string, error) | |||||
input := &obs.CreateSignedUrlInput{} | input := &obs.CreateSignedUrlInput{} | ||||
input.Bucket = setting.Bucket | input.Bucket = setting.Bucket | ||||
input.Key = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir, fileName), "/") | input.Key = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir, fileName), "/") | ||||
input.Expires = 60 * 60 | input.Expires = 60 * 60 | ||||
input.Method = obs.HttpMethodGet | input.Method = obs.HttpMethodGet | ||||
@@ -218,6 +218,7 @@ show_only_private = Showing only private | |||||
show_only_public = Showing only public | show_only_public = Showing only public | ||||
issues.in_your_repos = In your repositories | issues.in_your_repos = In your repositories | ||||
contributors = Contributors | |||||
[explore] | [explore] | ||||
repos = Repositories | repos = Repositories | ||||
@@ -2158,6 +2159,19 @@ repos.stars = Stars | |||||
repos.forks = Forks | repos.forks = Forks | ||||
repos.issues = Issues | repos.issues = Issues | ||||
repos.size = Size | repos.size = Size | ||||
repos.id=ID | |||||
repos.projectName=Project Name | |||||
repos.isPrivate=Private | |||||
repos.openi=OpenI | |||||
repos.visit=Visit | |||||
repos.download=Code Download | |||||
repos.pr=PR | |||||
repos.commit=Commit | |||||
repos.closedIssues=Closed Issue | |||||
repos.contributor=Contributor | |||||
repos.yes=Yes | |||||
repos.no=No | |||||
datasets.dataset_manage_panel= Dataset Manage | datasets.dataset_manage_panel= Dataset Manage | ||||
datasets.owner=Owner | datasets.owner=Owner | ||||
@@ -220,6 +220,8 @@ show_only_public=只显示公开的 | |||||
issues.in_your_repos=属于该用户项目的 | issues.in_your_repos=属于该用户项目的 | ||||
contributors=贡献者 | |||||
[explore] | [explore] | ||||
repos=项目 | repos=项目 | ||||
users=用户 | users=用户 | ||||
@@ -2160,6 +2162,19 @@ repos.stars=点赞数 | |||||
repos.forks=派生数 | repos.forks=派生数 | ||||
repos.issues=任务数 | repos.issues=任务数 | ||||
repos.size=大小 | repos.size=大小 | ||||
repos.id=ID | |||||
repos.projectName=项目名称 | |||||
repos.isPrivate=私有 | |||||
repos.openi=OpenI指数 | |||||
repos.visit=浏览量 | |||||
repos.download=代码下载量 | |||||
repos.pr=PR数 | |||||
repos.commit=Commit数 | |||||
repos.closedIssues=已解决任务数 | |||||
repos.contributor=贡献者数 | |||||
repos.yes=是 | |||||
repos.no=否 | |||||
datasets.dataset_manage_panel=数据集管理 | datasets.dataset_manage_panel=数据集管理 | ||||
datasets.owner=所有者 | datasets.owner=所有者 | ||||
@@ -527,8 +527,11 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
//Project board | //Project board | ||||
m.Group("/projectboard", func() { | m.Group("/projectboard", func() { | ||||
m.Get("/restoreFork", adminReq, repo.RestoreForkNumber) | |||||
m.Group("/project", func() { | m.Group("/project", func() { | ||||
m.Get("", adminReq, repo.GetAllProjectsPeriodStatistics) | m.Get("", adminReq, repo.GetAllProjectsPeriodStatistics) | ||||
m.Get("/download", adminReq, repo.ServeAllProjectsPeriodStatisticsFile) | |||||
m.Group("/:id", func() { | m.Group("/:id", func() { | ||||
m.Get("", adminReq, repo.GetProjectLatestStatistics) | m.Get("", adminReq, repo.GetProjectLatestStatistics) | ||||
m.Get("/period", adminReq, repo.GetProjectPeriodStatistics) | m.Get("/period", adminReq, repo.GetProjectPeriodStatistics) | ||||
@@ -1,8 +1,12 @@ | |||||
package repo | package repo | ||||
import ( | import ( | ||||
"encoding/csv" | |||||
"fmt" | "fmt" | ||||
"io/ioutil" | |||||
"net/http" | "net/http" | ||||
"os" | |||||
"path" | |||||
"strconv" | "strconv" | ||||
"time" | "time" | ||||
@@ -21,6 +25,7 @@ type ProjectsPeriodData struct { | |||||
LastUpdatedTime string `json:"lastUpdatedTime"` | LastUpdatedTime string `json:"lastUpdatedTime"` | ||||
PageSize int `json:"pageSize"` | PageSize int `json:"pageSize"` | ||||
TotalPage int `json:"totalPage"` | TotalPage int `json:"totalPage"` | ||||
TotalCount int64 `json:"totalCount"` | |||||
PageRecords []*models.RepoStatistic `json:"pageRecords"` | PageRecords []*models.RepoStatistic `json:"pageRecords"` | ||||
} | } | ||||
@@ -50,6 +55,19 @@ type ProjectLatestData struct { | |||||
Top10 []UserInfo `json:"top10"` | Top10 []UserInfo `json:"top10"` | ||||
} | } | ||||
func RestoreForkNumber(ctx *context.Context) { | |||||
repos, err := models.GetAllRepositories() | |||||
if err != nil { | |||||
log.Error("GetAllRepositories failed: %v", err.Error()) | |||||
return | |||||
} | |||||
for _, repo := range repos { | |||||
models.RestoreRepoStatFork(int64(repo.NumForks), repo.ID) | |||||
} | |||||
ctx.JSON(http.StatusOK, struct{}{}) | |||||
} | |||||
func GetAllProjectsPeriodStatistics(ctx *context.Context) { | func GetAllProjectsPeriodStatistics(ctx *context.Context) { | ||||
recordBeginTime, err := getRecordBeginTime() | recordBeginTime, err := getRecordBeginTime() | ||||
@@ -94,6 +112,7 @@ func GetAllProjectsPeriodStatistics(ctx *context.Context) { | |||||
RecordBeginTime: recordBeginTime.Format(DATE_FORMAT), | RecordBeginTime: recordBeginTime.Format(DATE_FORMAT), | ||||
PageSize: pageSize, | PageSize: pageSize, | ||||
TotalPage: getTotalPage(total, pageSize), | TotalPage: getTotalPage(total, pageSize), | ||||
TotalCount: total, | |||||
LastUpdatedTime: latestUpdatedTime, | LastUpdatedTime: latestUpdatedTime, | ||||
PageRecords: models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize)), | PageRecords: models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize)), | ||||
} | } | ||||
@@ -102,6 +121,125 @@ func GetAllProjectsPeriodStatistics(ctx *context.Context) { | |||||
} | } | ||||
func ServeAllProjectsPeriodStatisticsFile(ctx *context.Context) { | |||||
recordBeginTime, err := getRecordBeginTime() | |||||
if err != nil { | |||||
log.Error("Can not get record begin time", err) | |||||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err")) | |||||
return | |||||
} | |||||
beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime) | |||||
if err != nil { | |||||
log.Error("Parameter is wrong", err) | |||||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.parameter_is_wrong")) | |||||
return | |||||
} | |||||
q := ctx.QueryTrim("q") | |||||
page := ctx.QueryInt("page") | |||||
if page <= 0 { | |||||
page = 1 | |||||
} | |||||
pageSize := 1000 | |||||
orderBy := getOrderBy(ctx) | |||||
_, latestDate, err := models.GetRepoStatLastUpdatedTime() | |||||
if err != nil { | |||||
log.Error("Can not query the last updated time.", err) | |||||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.last_update_time_error")) | |||||
return | |||||
} | |||||
countSql := generateCountSql(beginTime, endTime, latestDate, q) | |||||
total, err := models.CountRepoStatByRawSql(countSql) | |||||
if err != nil { | |||||
log.Error("Can not query total count.", err) | |||||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error")) | |||||
return | |||||
} | |||||
fileName := getFileName(ctx, beginTime, endTime) | |||||
if err := os.MkdirAll(setting.RadarMap.Path, os.ModePerm); err != nil { | |||||
ctx.Error(http.StatusBadRequest, fmt.Errorf("Failed to create dir %s: %v", setting.AvatarUploadPath, err).Error()) | |||||
} | |||||
totalPage := getTotalPage(total, pageSize) | |||||
f, e := os.Create(fileName) | |||||
defer f.Close() | |||||
if e != nil { | |||||
log.Warn("Failed to create file", e) | |||||
} | |||||
writer := csv.NewWriter(f) | |||||
writer.Write(allProjectsPeroidHeader(ctx)) | |||||
for i := 0; i <= totalPage; i++ { | |||||
pageRecords := models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, i+1, pageSize)) | |||||
for _, record := range pageRecords { | |||||
e = writer.Write(allProjectsPeroidValues(record, ctx)) | |||||
if e != nil { | |||||
log.Warn("Failed to write record", e) | |||||
} | |||||
} | |||||
writer.Flush() | |||||
} | |||||
ctx.ServeFile(fileName) | |||||
} | |||||
func getFileName(ctx *context.Context, beginTime time.Time, endTime time.Time) string { | |||||
baseName := setting.RadarMap.Path + "/" | |||||
if ctx.QueryTrim("type") != "" { | |||||
baseName = baseName + ctx.QueryTrim("type") + "_" | |||||
} | |||||
if ctx.QueryTrim("q") != "" { | |||||
baseName = baseName + ctx.QueryTrim("q") + "_" | |||||
} | |||||
baseName = baseName + beginTime.Format(DATE_FORMAT) + "_to_" + endTime.Format(DATE_FORMAT) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + ".csv" | |||||
return baseName | |||||
} | |||||
func ClearUnusedStatisticsFile() { | |||||
fileInfos, err := ioutil.ReadDir(setting.RadarMap.Path) | |||||
if err != nil { | |||||
log.Warn("can not read dir: "+setting.RadarMap.Path, err) | |||||
return | |||||
} | |||||
for _, fileInfo := range fileInfos { | |||||
if !fileInfo.IsDir() && fileInfo.ModTime().Before(time.Now().AddDate(0, 0, -1)) { | |||||
os.Remove(path.Join(setting.RadarMap.Path, fileInfo.Name())) | |||||
} | |||||
} | |||||
} | |||||
func allProjectsPeroidHeader(ctx *context.Context) []string { | |||||
return []string{ctx.Tr("repos.id"), ctx.Tr("repos.projectName"), ctx.Tr("repos.isPrivate"), ctx.Tr("repos.openi"), ctx.Tr("repos.visit"), ctx.Tr("repos.download"), ctx.Tr("repos.pr"), ctx.Tr("repos.commit"), | |||||
ctx.Tr("repos.watches"), ctx.Tr("repos.stars"), ctx.Tr("repos.forks"), ctx.Tr("repos.issues"), ctx.Tr("repos.closedIssues"), ctx.Tr("repos.contributor")} | |||||
} | |||||
func allProjectsPeroidValues(rs *models.RepoStatistic, ctx *context.Context) []string { | |||||
return []string{strconv.FormatInt(rs.RepoID, 10), rs.Name, getIsPrivateDisplay(rs.IsPrivate, ctx), strconv.FormatFloat(rs.RadarTotal, 'f', 2, 64), | |||||
strconv.FormatInt(rs.NumVisits, 10), strconv.FormatInt(rs.NumDownloads, 10), strconv.FormatInt(rs.NumPulls, 10), strconv.FormatInt(rs.NumCommits, 10), | |||||
strconv.FormatInt(rs.NumWatches, 10), strconv.FormatInt(rs.NumStars, 10), strconv.FormatInt(rs.NumForks, 10), strconv.FormatInt(rs.NumIssues, 10), | |||||
strconv.FormatInt(rs.NumClosedIssues, 10), strconv.FormatInt(rs.NumContributor, 10), | |||||
} | |||||
} | |||||
func getIsPrivateDisplay(private bool, ctx *context.Context) string { | |||||
if private { | |||||
return ctx.Tr("repos.yes") | |||||
} else { | |||||
return ctx.Tr("repos.no") | |||||
} | |||||
} | |||||
func GetProjectLatestStatistics(ctx *context.Context) { | func GetProjectLatestStatistics(ctx *context.Context) { | ||||
repoId := ctx.Params(":id") | repoId := ctx.Params(":id") | ||||
recordBeginTime, err := getRecordBeginTime() | recordBeginTime, err := getRecordBeginTime() | ||||
@@ -151,6 +289,15 @@ func GetProjectLatestStatistics(ctx *context.Context) { | |||||
for _, contributor := range contributors { | for _, contributor := range contributors { | ||||
mode := repository.GetCollaboratorMode(contributor.UserId) | mode := repository.GetCollaboratorMode(contributor.UserId) | ||||
if mode == -1 { | |||||
if contributor.IsAdmin { | |||||
mode = int(models.AccessModeAdmin) | |||||
} | |||||
if contributor.UserId == repository.OwnerID { | |||||
mode = int(models.AccessModeOwner) | |||||
} | |||||
} | |||||
pr := models.GetPullCountByUserAndRepoId(repoIdInt, contributor.UserId) | pr := models.GetPullCountByUserAndRepoId(repoIdInt, contributor.UserId) | ||||
userInfo := UserInfo{ | userInfo := UserInfo{ | ||||
User: contributor.Committer, | User: contributor.Committer, | ||||
@@ -317,6 +317,12 @@ func HTTP(ctx *context.Context) { | |||||
go repo.IncreaseCloneCnt() | go repo.IncreaseCloneCnt() | ||||
} | } | ||||
if ctx.Req.Method == "POST" { | |||||
if strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") { | |||||
go repo.IncreaseGitCloneCnt() | |||||
} | |||||
} | |||||
w := ctx.Resp | w := ctx.Resp | ||||
r := ctx.Req.Request | r := ctx.Req.Request | ||||
cfg := &serviceConfig{ | cfg := &serviceConfig{ | ||||
@@ -93,6 +93,7 @@ func RepoStatisticDaily(date string) { | |||||
IsPrivate: repo.IsPrivate, | IsPrivate: repo.IsPrivate, | ||||
NumWatches: int64(repo.NumWatches), | NumWatches: int64(repo.NumWatches), | ||||
NumStars: int64(repo.NumStars), | NumStars: int64(repo.NumStars), | ||||
NumForks: int64(repo.NumForks), | |||||
NumDownloads: repo.CloneCnt, | NumDownloads: repo.CloneCnt, | ||||
NumComments: numComments, | NumComments: numComments, | ||||
NumVisits: int64(numVisits), | NumVisits: int64(numVisits), | ||||
@@ -1,13 +1,27 @@ | |||||
package repo | package repo | ||||
import ( | import ( | ||||
"fmt" | |||||
"net/http" | |||||
"time" | "time" | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/context" | |||||
"code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
) | ) | ||||
func QueryUserStaticData(ctx *context.Context) { | |||||
startDate := ctx.Query("startDate") | |||||
endDate := ctx.Query("endDate") | |||||
log.Info("startDate=" + startDate + " endDate=" + endDate) | |||||
startTime, _ := time.Parse("2006-01-02", startDate) | |||||
endTime, _ := time.Parse("2006-01-02", endDate) | |||||
log.Info("startTime=" + fmt.Sprint(startTime.Unix()) + " endDate=" + fmt.Sprint(endTime.Unix())) | |||||
ctx.JSON(http.StatusOK, models.QueryUserStaticData(startTime.Unix(), endTime.Unix())) | |||||
} | |||||
func TimingCountDataByDate(date string) { | func TimingCountDataByDate(date string) { | ||||
t, _ := time.Parse("2006-01-02", date) | t, _ := time.Parse("2006-01-02", date) | ||||
@@ -12,6 +12,7 @@ import ( | |||||
"fmt" | "fmt" | ||||
gotemplate "html/template" | gotemplate "html/template" | ||||
"io/ioutil" | "io/ioutil" | ||||
"net/http" | |||||
"net/url" | "net/url" | ||||
"path" | "path" | ||||
"strings" | "strings" | ||||
@@ -31,11 +32,12 @@ import ( | |||||
) | ) | ||||
const ( | const ( | ||||
tplRepoEMPTY base.TplName = "repo/empty" | |||||
tplRepoHome base.TplName = "repo/home" | |||||
tplWatchers base.TplName = "repo/watchers" | |||||
tplForks base.TplName = "repo/forks" | |||||
tplMigrating base.TplName = "repo/migrating" | |||||
tplRepoEMPTY base.TplName = "repo/empty" | |||||
tplRepoHome base.TplName = "repo/home" | |||||
tplWatchers base.TplName = "repo/watchers" | |||||
tplForks base.TplName = "repo/forks" | |||||
tplMigrating base.TplName = "repo/migrating" | |||||
tplContributors base.TplName = "repo/contributors" | |||||
) | ) | ||||
type namedBlob struct { | type namedBlob struct { | ||||
@@ -575,19 +577,29 @@ func safeURL(address string) string { | |||||
} | } | ||||
type ContributorInfo struct { | type ContributorInfo struct { | ||||
UserInfo *models.User // nil for contributor who is not a registered user | |||||
Email string | |||||
CommitCnt int | |||||
UserInfo *models.User // nil for contributor who is not a registered user | |||||
RelAvatarLink string `json:"rel_avatar_link"` | |||||
UserName string `json:"user_name"` | |||||
Email string `json:"email"` | |||||
CommitCnt int `json:"commit_cnt"` | |||||
} | } | ||||
func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo{ | |||||
type GetContributorsInfo struct { | |||||
ErrorCode int `json:"error_code"` | |||||
ErrorMsg string `json:"error_msg"` | |||||
Count int `json:"count"` | |||||
ContributorInfo []*ContributorInfo `json:"contributor_info"` | |||||
} | |||||
func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo { | |||||
for _, c := range contributorInfos { | for _, c := range contributorInfos { | ||||
if strings.Compare(c.Email,email) == 0 { | |||||
if strings.Compare(c.Email, email) == 0 { | |||||
return c | return c | ||||
} | } | ||||
} | } | ||||
return nil | return nil | ||||
} | } | ||||
// Home render repository home page | // Home render repository home page | ||||
func Home(ctx *context.Context) { | func Home(ctx *context.Context) { | ||||
if len(ctx.Repo.Units) > 0 { | if len(ctx.Repo.Units) > 0 { | ||||
@@ -596,35 +608,41 @@ func Home(ctx *context.Context) { | |||||
if err == nil && contributors != nil { | if err == nil && contributors != nil { | ||||
startTime := time.Now() | startTime := time.Now() | ||||
var contributorInfos []*ContributorInfo | var contributorInfos []*ContributorInfo | ||||
contributorInfoHash:= make(map[string]*ContributorInfo) | |||||
contributorInfoHash := make(map[string]*ContributorInfo) | |||||
count := 0 | |||||
for _, c := range contributors { | for _, c := range contributors { | ||||
if strings.Compare(c.Email,"") == 0 { | |||||
if count >= 25 { | |||||
continue | |||||
} | |||||
if strings.Compare(c.Email, "") == 0 { | |||||
continue | continue | ||||
} | } | ||||
// get user info from committer email | // get user info from committer email | ||||
user, err := models.GetUserByActivateEmail(c.Email) | user, err := models.GetUserByActivateEmail(c.Email) | ||||
if err == nil { | if err == nil { | ||||
// committer is system user, get info through user's primary email | // committer is system user, get info through user's primary email | ||||
if existedContributorInfo,ok:=contributorInfoHash[user.Email];ok { | |||||
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok { | |||||
// existed: same primary email, different committer name | // existed: same primary email, different committer name | ||||
existedContributorInfo.CommitCnt += c.CommitCnt | existedContributorInfo.CommitCnt += c.CommitCnt | ||||
}else{ | |||||
} else { | |||||
// new committer info | // new committer info | ||||
var newContributor = &ContributorInfo{ | var newContributor = &ContributorInfo{ | ||||
user, user.Email,c.CommitCnt, | |||||
user, user.RelAvatarLink(), user.Name, user.Email, c.CommitCnt, | |||||
} | } | ||||
contributorInfos = append(contributorInfos, newContributor ) | |||||
count++ | |||||
contributorInfos = append(contributorInfos, newContributor) | |||||
contributorInfoHash[user.Email] = newContributor | contributorInfoHash[user.Email] = newContributor | ||||
} | } | ||||
} else { | } else { | ||||
// committer is not system user | // committer is not system user | ||||
if existedContributorInfo,ok:=contributorInfoHash[c.Email];ok { | |||||
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok { | |||||
// existed: same primary email, different committer name | // existed: same primary email, different committer name | ||||
existedContributorInfo.CommitCnt += c.CommitCnt | existedContributorInfo.CommitCnt += c.CommitCnt | ||||
}else{ | |||||
} else { | |||||
var newContributor = &ContributorInfo{ | var newContributor = &ContributorInfo{ | ||||
user, c.Email,c.CommitCnt, | |||||
user, "", "",c.Email, c.CommitCnt, | |||||
} | } | ||||
count++ | |||||
contributorInfos = append(contributorInfos, newContributor) | contributorInfos = append(contributorInfos, newContributor) | ||||
contributorInfoHash[c.Email] = newContributor | contributorInfoHash[c.Email] = newContributor | ||||
} | } | ||||
@@ -632,7 +650,7 @@ func Home(ctx *context.Context) { | |||||
} | } | ||||
ctx.Data["ContributorInfo"] = contributorInfos | ctx.Data["ContributorInfo"] = contributorInfos | ||||
var duration = time.Since(startTime) | var duration = time.Since(startTime) | ||||
log.Info("getContributorInfo cost: %v seconds",duration.Seconds()) | |||||
log.Info("getContributorInfo cost: %v seconds", duration.Seconds()) | |||||
} | } | ||||
if ctx.Repo.Repository.IsBeingCreated() { | if ctx.Repo.Repository.IsBeingCreated() { | ||||
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID) | task, err := models.GetMigratingTask(ctx.Repo.Repository.ID) | ||||
@@ -699,13 +717,13 @@ func renderLicense(ctx *context.Context) { | |||||
log.Error("failed to get license content: %v, err:%v", f, err) | log.Error("failed to get license content: %v, err:%v", f, err) | ||||
continue | continue | ||||
} | } | ||||
if bytes.Compare(buf,license) == 0 { | |||||
log.Info("got matched license:%v",f) | |||||
if bytes.Compare(buf, license) == 0 { | |||||
log.Info("got matched license:%v", f) | |||||
ctx.Data["LICENSE"] = f | ctx.Data["LICENSE"] = f | ||||
return | return | ||||
} | } | ||||
} | } | ||||
log.Info("not found matched license,repo:%v",ctx.Repo.Repository.Name) | |||||
log.Info("not found matched license,repo:%v", ctx.Repo.Repository.Name) | |||||
} | } | ||||
func renderLanguageStats(ctx *context.Context) { | func renderLanguageStats(ctx *context.Context) { | ||||
@@ -806,31 +824,31 @@ func renderCode(ctx *context.Context) { | |||||
baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath()) | baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath()) | ||||
defer baseGitRepo.Close() | defer baseGitRepo.Close() | ||||
if err != nil { | if err != nil { | ||||
log.Error("error open baseRepo:%s",ctx.Repo.Repository.BaseRepo.RepoPath()) | |||||
log.Error("error open baseRepo:%s", ctx.Repo.Repository.BaseRepo.RepoPath()) | |||||
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error | ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error | ||||
}else{ | |||||
if _,error:= baseGitRepo.GetBranch(ctx.Repo.BranchName);error==nil{ | |||||
} 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 | //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) | |||||
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName | |||||
ctx.SetParams("*", compareUrl) | |||||
ctx.Data["UpstreamSameBranchName"] = true | ctx.Data["UpstreamSameBranchName"] = true | ||||
}else{ | |||||
} else { | |||||
//else, compare between current repo branch and base repo's default branch | //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) | |||||
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 | ctx.Data["UpstreamSameBranchName"] = false | ||||
} | } | ||||
_, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx) | _, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx) | ||||
defer headGitRepo.Close() | defer headGitRepo.Close() | ||||
if compareInfo!= nil { | |||||
if compareInfo.Commits!=nil { | |||||
log.Info("compareInfoCommits数量:%d",compareInfo.Commits.Len()) | |||||
if compareInfo != nil { | |||||
if compareInfo.Commits != nil { | |||||
log.Info("compareInfoCommits数量:%d", compareInfo.Commits.Len()) | |||||
ctx.Data["FetchUpstreamCnt"] = compareInfo.Commits.Len() | ctx.Data["FetchUpstreamCnt"] = compareInfo.Commits.Len() | ||||
}else{ | |||||
} else { | |||||
log.Info("compareInfo nothing different") | log.Info("compareInfo nothing different") | ||||
ctx.Data["FetchUpstreamCnt"] = 0 | ctx.Data["FetchUpstreamCnt"] = 0 | ||||
} | } | ||||
}else{ | |||||
} else { | |||||
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error | ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error | ||||
} | } | ||||
} | } | ||||
@@ -898,3 +916,64 @@ func Forks(ctx *context.Context) { | |||||
ctx.HTML(200, tplForks) | ctx.HTML(200, tplForks) | ||||
} | } | ||||
func Contributors(ctx *context.Context) { | |||||
ctx.HTML(http.StatusOK, tplContributors) | |||||
} | |||||
func ContributorsAPI(ctx *context.Context) { | |||||
count := 0 | |||||
errorCode := 0 | |||||
errorMsg := "" | |||||
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath()) | |||||
var contributorInfos []*ContributorInfo | |||||
if err == nil && contributors != nil { | |||||
contributorInfoHash := make(map[string]*ContributorInfo) | |||||
for _, c := range contributors { | |||||
if strings.Compare(c.Email, "") == 0 { | |||||
continue | |||||
} | |||||
// get user info from committer email | |||||
user, err := models.GetUserByActivateEmail(c.Email) | |||||
if err == nil { | |||||
// committer is system user, get info through user's primary email | |||||
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok { | |||||
// existed: same primary email, different committer name | |||||
existedContributorInfo.CommitCnt += c.CommitCnt | |||||
} else { | |||||
// new committer info | |||||
var newContributor = &ContributorInfo{ | |||||
user, user.RelAvatarLink(),user.Name, user.Email,c.CommitCnt, | |||||
} | |||||
count++ | |||||
contributorInfos = append(contributorInfos, newContributor) | |||||
contributorInfoHash[user.Email] = newContributor | |||||
} | |||||
} else { | |||||
// committer is not system user | |||||
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok { | |||||
// existed: same primary email, different committer name | |||||
existedContributorInfo.CommitCnt += c.CommitCnt | |||||
} else { | |||||
var newContributor = &ContributorInfo{ | |||||
user, "", "",c.Email,c.CommitCnt, | |||||
} | |||||
count++ | |||||
contributorInfos = append(contributorInfos, newContributor) | |||||
contributorInfoHash[c.Email] = newContributor | |||||
} | |||||
} | |||||
} | |||||
} else { | |||||
log.Error("GetContributors failed: %v", err) | |||||
errorCode = -1 | |||||
errorMsg = err.Error() | |||||
} | |||||
ctx.JSON(http.StatusOK, GetContributorsInfo{ | |||||
ErrorCode: errorCode, | |||||
ErrorMsg: errorMsg, | |||||
Count: count, | |||||
ContributorInfo: contributorInfos, | |||||
}) | |||||
} |
@@ -791,9 +791,11 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
}, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef()) | }, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef()) | ||||
m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action) | m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action) | ||||
m.Get("/tool/query_user_static", repo.QueryUserStaticData) | |||||
// Grouping for those endpoints not requiring authentication | // Grouping for those endpoints not requiring authentication | ||||
m.Group("/:username/:reponame", func() { | m.Group("/:username/:reponame", func() { | ||||
m.Get("/contributors", repo.Contributors) | |||||
m.Get("/contributors/list", repo.ContributorsAPI) | |||||
m.Group("/milestone", func() { | m.Group("/milestone", func() { | ||||
m.Get("/:id", repo.MilestoneIssuesAndPulls) | m.Get("/:id", repo.MilestoneIssuesAndPulls) | ||||
}, reqRepoIssuesOrPullsReader, context.RepoRef()) | }, reqRepoIssuesOrPullsReader, context.RepoRef()) | ||||
@@ -180,8 +180,8 @@ | |||||
var _hmt = _hmt || []; | var _hmt = _hmt || []; | ||||
(function() { | (function() { | ||||
var hm = document.createElement("script"); | var hm = document.createElement("script"); | ||||
hm.src = "https://hm.baidu.com/hm.js?7c4ef0a24be6109ab22e63c832ab21cf"; | |||||
var s = document.getElementsByTagName("script")[0]; | |||||
hm.src = "https://hm.baidu.com/hm.js?46149a0b61fdeddfe427ff4de63794ba"; | |||||
var s = document.getElementsByTagName("script")[0]; | |||||
s.parentNode.insertBefore(hm, s); | s.parentNode.insertBefore(hm, s); | ||||
})(); | })(); | ||||
</script> | </script> | ||||
@@ -181,8 +181,8 @@ | |||||
var _hmt = _hmt || []; | var _hmt = _hmt || []; | ||||
(function() { | (function() { | ||||
var hm = document.createElement("script"); | var hm = document.createElement("script"); | ||||
hm.src = "https://hm.baidu.com/hm.js?7c4ef0a24be6109ab22e63c832ab21cf"; | |||||
var s = document.getElementsByTagName("script")[0]; | |||||
hm.src = "https://hm.baidu.com/hm.js?46149a0b61fdeddfe427ff4de63794ba"; | |||||
var s = document.getElementsByTagName("script")[0]; | |||||
s.parentNode.insertBefore(hm, s); | s.parentNode.insertBefore(hm, s); | ||||
})(); | })(); | ||||
</script> | </script> | ||||
@@ -39,7 +39,7 @@ | |||||
<h4 class="ui top attached header"> | <h4 class="ui top attached header"> | ||||
<strong>{{.i18n.Tr "org.people"}}</strong> | <strong>{{.i18n.Tr "org.people"}}</strong> | ||||
<div class="ui right"> | <div class="ui right"> | ||||
<a class="text grey" href="{{.OrgLink}}/members">{{.Org.NumMembers}} {{svg "octicon-chevron-right" 16}}</a> | |||||
<a class="text grey" href="{{.OrgLink}}/members">{{.MembersTotal}} {{svg "octicon-chevron-right" 16}}</a> | |||||
</div> | </div> | ||||
<!-- {{if .IsOrganizationMember}} --> | <!-- {{if .IsOrganizationMember}} --> | ||||
@@ -0,0 +1,9 @@ | |||||
{{template "base/head" .}} | |||||
<div class="repository watchers"> | |||||
{{template "repo/header" .}} | |||||
<div class="ui container" id="Contributors"> | |||||
</div> | |||||
</div> | |||||
{{template "base/footer" .}} |
@@ -4,7 +4,7 @@ | |||||
font-size: 1.0em; | font-size: 1.0em; | ||||
margin-bottom: 1.0rem; | margin-bottom: 1.0rem; | ||||
} | } | ||||
#contributorInfo > a:nth-child(n+25){ | |||||
#contributorInfo > a:nth-child(n+26){ | |||||
display:none; | display:none; | ||||
} | } | ||||
#contributorInfo > a{ | #contributorInfo > a{ | ||||
@@ -329,9 +329,15 @@ | |||||
<div> | <div> | ||||
<h4 class="ui header"> | <h4 class="ui header"> | ||||
{{$lenCon := len .ContributorInfo}} | |||||
{{if lt $lenCon 25 }} | |||||
<strong>贡献者 ({{len .ContributorInfo}})</strong> | <strong>贡献者 ({{len .ContributorInfo}})</strong> | ||||
{{else}} | |||||
<strong>贡献者 ({{len .ContributorInfo}}+)</strong> | |||||
{{end}} | |||||
<div class="ui right"> | <div class="ui right"> | ||||
<a class="membersmore text grey" href="javascript:;">全部 {{svg "octicon-chevron-right" 16}}</a> | |||||
<a class="membersmore text grey" href="{{.RepoLink}}/contributors">全部 {{svg "octicon-chevron-right" 16}}</a> | |||||
</div> | </div> | ||||
</h4> | </h4> | ||||
<div class="ui members" id="contributorInfo"> | <div class="ui members" id="contributorInfo"> | ||||
@@ -353,10 +359,10 @@ | |||||
</div> | </div> | ||||
<script type="text/javascript"> | <script type="text/javascript"> | ||||
$(document).ready(function(){ | |||||
$(".membersmore").click(function(){ | |||||
$("#contributorInfo > a:nth-child(n+25)").show(); | |||||
}); | |||||
}); | |||||
// $(document).ready(function(){ | |||||
// $(".membersmore").click(function(){ | |||||
// $("#contributorInfo > a:nth-child(n+25)").show(); | |||||
// }); | |||||
// }); | |||||
</script> | </script> | ||||
{{template "base/footer" .}} | {{template "base/footer" .}} |
@@ -0,0 +1,109 @@ | |||||
<template> | |||||
<div class="ui container"> | |||||
<div class="row git-user-content"> | |||||
<h3 class="ui header"> | |||||
<div class="ui breadcrumb"> | |||||
<a class="section" href="/">代码</a> | |||||
<div class="divider"> / </div> | |||||
<div class="active section" >贡献者({{totalNum}})</div> | |||||
</div> | |||||
</h3> | |||||
<div class="ui horizontal relaxed list"> | |||||
<div class="item user-list-item" v-for="(contributor,i) in contributors_list_page" > | |||||
<a v-if="contributor.user_name" :href="AppSubUrl +'/'+ contributor.user_name"><img class="ui avatar s16 image js-popover-card" :src="contributor.rel_avatar_link"></a> | |||||
<a v-else :href="'mailto:' + contributor.email "><img class="ui avatar s16 image js-popover-card" :avatar="contributor.email"></a> | |||||
<div class="content"> | |||||
<div class="header" > | |||||
<a v-if="contributor.user_name" :href="AppSubUrl +'/'+ contributor.user_name">{{contributor.user_name}}</a> | |||||
<a v-else :href="'mailto:' + contributor.email ">{{contributor.email}}</a> | |||||
</div> | |||||
<span class="commit-btn">Commits: {{contributor.commit_cnt}}</span> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="ui container" style="margin-top:50px;text-align:center"> | |||||
<el-pagination | |||||
background | |||||
@current-change="handleCurrentChange" | |||||
:current-page="currentPage" | |||||
:page-size="pageSize" | |||||
layout="total, prev, pager, next" | |||||
:total="totalNum"> | |||||
</el-pagination> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | |||||
export default { | |||||
data() { | |||||
return { | |||||
url:'', | |||||
contributors_list:[], | |||||
contributors_list_page:[], | |||||
currentPage:1, | |||||
pageSize:50, | |||||
totalNum:0, | |||||
AppSubUrl:AppSubUrl | |||||
}; | |||||
}, | |||||
methods: { | |||||
getContributorsList(){ | |||||
this.$axios.get(this.url+'/contributors/list').then((res)=>{ | |||||
this.contributors_list = res.data.contributor_info | |||||
this.totalNum = this.contributors_list.length | |||||
this.contributors_list_page = this.contributors_list.slice(0,this.pageSize) | |||||
}) | |||||
}, | |||||
handleCurrentChange(val){ | |||||
this.contributors_list_page = this.contributors_list.slice((val-1)*this.pageSize,val*this.pageSize) | |||||
}, | |||||
}, | |||||
computed:{ | |||||
}, | |||||
watch: { | |||||
}, | |||||
created(){ | |||||
this.url=document.head.querySelector("[property~='og:url'][content]").content | |||||
this.getContributorsList() | |||||
}, | |||||
updated(){ | |||||
if(document.querySelectorAll('img[avatar]').length!==0){ | |||||
window.LetterAvatar.transform() | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style scoped> | |||||
/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active { | |||||
background-color: #5bb973; | |||||
color: #FFF; | |||||
} | |||||
/deep/ .el-pagination.is-background .el-pager li.active { | |||||
color: #fff; | |||||
cursor: default; | |||||
} | |||||
/deep/ .el-pagination.is-background .el-pager li:hover { | |||||
color: #5bb973; | |||||
} | |||||
/deep/ .el-pagination.is-background .el-pager li:not(.disabled):hover { | |||||
color: #5bb973; | |||||
} | |||||
/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active:hover { | |||||
background-color: #5bb973; | |||||
color: #FFF; | |||||
} | |||||
</style> |
@@ -0,0 +1,74 @@ | |||||
/** | |||||
* LetterAvatar | |||||
* | |||||
* Artur Heinze | |||||
* Create Letter avatar based on Initials | |||||
* based on https://gist.github.com/leecrossley/6027780 | |||||
*/ | |||||
(function(w, d){ | |||||
function LetterAvatar (name, size, color) { | |||||
name = name || ''; | |||||
size = size || 60; | |||||
var colours = [ | |||||
"#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50", | |||||
"#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d" | |||||
], | |||||
nameSplit = String(name).split(' '), | |||||
initials, charIndex, colourIndex, canvas, context, dataURI; | |||||
if (nameSplit.length == 1) { | |||||
initials = nameSplit[0] ? nameSplit[0].charAt(0):'?'; | |||||
} else { | |||||
initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0); | |||||
} | |||||
if (w.devicePixelRatio) { | |||||
size = (size * w.devicePixelRatio); | |||||
} | |||||
charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64; | |||||
colourIndex = charIndex % 20; | |||||
canvas = d.createElement('canvas'); | |||||
canvas.width = size; | |||||
canvas.height = size; | |||||
context = canvas.getContext("2d"); | |||||
context.fillStyle = color ? color : colours[colourIndex - 1]; | |||||
context.fillRect (0, 0, canvas.width, canvas.height); | |||||
context.font = Math.round(canvas.width/2)+"px 'Microsoft Yahei'"; | |||||
context.textAlign = "center"; | |||||
context.fillStyle = "#FFF"; | |||||
context.fillText(initials, size / 2, size / 1.5); | |||||
dataURI = canvas.toDataURL(); | |||||
canvas = null; | |||||
return dataURI; | |||||
} | |||||
LetterAvatar.transform = function() { | |||||
Array.prototype.forEach.call(d.querySelectorAll('img[avatar]'), function(img, name, color) { | |||||
name = img.getAttribute('avatar'); | |||||
color = img.getAttribute('color'); | |||||
img.src = LetterAvatar(name, img.getAttribute('width'), color); | |||||
img.removeAttribute('avatar'); | |||||
img.setAttribute('alt', name); | |||||
}); | |||||
}; | |||||
// AMD support | |||||
if (typeof define === 'function' && define.amd) { | |||||
define(function () { return LetterAvatar; }); | |||||
// CommonJS and Node.js module support. | |||||
} else if (typeof exports !== 'undefined') { | |||||
// Support Node.js specific `module.exports` (which can be a function) | |||||
if (typeof module != 'undefined' && module.exports) { | |||||
exports = module.exports = LetterAvatar; | |||||
} | |||||
// But always support CommonJS module 1.1.1 spec (`exports` cannot be a function) | |||||
exports.LetterAvatar = LetterAvatar; | |||||
} else { | |||||
window.LetterAvatar = LetterAvatar; | |||||
d.addEventListener('DOMContentLoaded', function(event) { | |||||
LetterAvatar.transform(); | |||||
}); | |||||
} | |||||
})(window, document); |
@@ -4,7 +4,7 @@ | |||||
import './publicpath.js'; | import './publicpath.js'; | ||||
import './polyfills.js'; | import './polyfills.js'; | ||||
import './features/letteravatar.js' | |||||
import Vue from 'vue'; | import Vue from 'vue'; | ||||
import ElementUI from 'element-ui'; | import ElementUI from 'element-ui'; | ||||
import 'element-ui/lib/theme-chalk/index.css'; | import 'element-ui/lib/theme-chalk/index.css'; | ||||
@@ -37,6 +37,7 @@ 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' | import EditTopics from './components/EditTopics.vue' | ||||
import Contributors from './components/Contributors.vue' | |||||
Vue.use(ElementUI); | Vue.use(ElementUI); | ||||
Vue.prototype.$axios = axios; | Vue.prototype.$axios = axios; | ||||
@@ -2969,6 +2970,7 @@ $(document).ready(async () => { | |||||
initObsUploader(); | initObsUploader(); | ||||
initVueEditAbout(); | initVueEditAbout(); | ||||
initVueEditTopic(); | initVueEditTopic(); | ||||
initVueContributors(); | |||||
initVueImages(); | initVueImages(); | ||||
initTeamSettings(); | initTeamSettings(); | ||||
initCtrlEnterSubmit(); | initCtrlEnterSubmit(); | ||||
@@ -3682,6 +3684,21 @@ function initVueEditTopic() { | |||||
render:h=>h(EditTopics) | render:h=>h(EditTopics) | ||||
}) | }) | ||||
} | } | ||||
function initVueContributors() { | |||||
const el = document.getElementById('Contributors'); | |||||
if (!el) { | |||||
return; | |||||
} | |||||
new Vue({ | |||||
el:'#Contributors', | |||||
render:h=>h(Contributors) | |||||
}) | |||||
} | |||||
function initVueImages() { | function initVueImages() { | ||||
const el = document.getElementById('images'); | const el = document.getElementById('images'); | ||||
console.log("el",el) | console.log("el",el) | ||||
@@ -243,6 +243,48 @@ footer .column{margin-bottom:0!important; padding-bottom:0!important;} | |||||
display: inline-block; | display: inline-block; | ||||
width: 100%; | width: 100%; | ||||
} | } | ||||
.git-user-content{ | |||||
margin-bottom: 16px; | |||||
word-break: break-all; | |||||
} | |||||
.row.git-user-content .ui.avatar.s16.image { | |||||
width: 50px !important; | |||||
height: 50px !important; | |||||
vertical-align: middle; | |||||
border-radius: 500rem;} | |||||
.user-list-item { | |||||
padding: 0.3em 0px !important; | |||||
margin: 15px 0 !important; | |||||
width: 220px !important; | |||||
} | |||||
.row.git-user-content .content { | |||||
margin-left: 6px; | |||||
display: inline-block; | |||||
vertical-align: top !important; | |||||
overflow: hidden; | |||||
} | |||||
.row.git-user-content .content .header { | |||||
font-weight: bold; | |||||
} | |||||
.item.user-list-item .header { | |||||
line-height: 23px !important; | |||||
font-size: 16px !important; | |||||
text-overflow: ellipsis; | |||||
overflow: hidden; | |||||
width: 145px; | |||||
white-space:nowrap; | |||||
} | |||||
.item.user-list-item .header a { | |||||
color: #587284 !important; | |||||
} | |||||
.row.git-user-content .content .commit-btn { | |||||
margin-top: 6px; | |||||
float: left; | |||||
font-size: 11px; | |||||
color: #40485b !important; | |||||
} | |||||
.dropdown-menu { | .dropdown-menu { | ||||
position: relative; | position: relative; | ||||