@@ -65,6 +65,11 @@ type Cloudbrain struct { | |||||
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 +528,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 +588,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() | ||||
@@ -136,7 +136,9 @@ func init() { | |||||
) | ) | ||||
tablesStatistic = append(tablesStatistic, | tablesStatistic = append(tablesStatistic, | ||||
new(FileChunk)) | |||||
new(FileChunk), | |||||
new(UserBusinessAnalysis), | |||||
) | |||||
gonicNames := []string{"SSL", "UID"} | gonicNames := []string{"SSL", "UID"} | ||||
for _, name := range gonicNames { | for _, name := range gonicNames { | ||||
@@ -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 | ||||
} | } | ||||
@@ -0,0 +1,346 @@ | |||||
package models | |||||
import ( | |||||
"fmt" | |||||
"time" | |||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/timeutil" | |||||
) | |||||
type UserBusinessAnalysis struct { | |||||
ID int64 `xorm:"pk"` | |||||
CountDate int64 `xorm:"pk"` | |||||
//action :ActionMergePullRequest // 11 | |||||
CodeMergeCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//action :ActionCommitRepo // 5 | |||||
CommitCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//action :ActionCommentIssue // 10 | |||||
IssueCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//comment table current date | |||||
CommentCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//watch table current date | |||||
FocusRepoCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//star table current date | |||||
StarRepoCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//follow table | |||||
WatchedCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
// user table | |||||
GiteaAgeMonth int `xorm:"NOT NULL DEFAULT 0"` | |||||
// | |||||
CommitCodeSize int `xorm:"NOT NULL DEFAULT 0"` | |||||
//attachement table | |||||
CommitDatasetSize int `xorm:"NOT NULL DEFAULT 0"` | |||||
//0 | |||||
CommitModelCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//issue, issueassignees | |||||
SolveIssueCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//baike | |||||
EncyclopediasCount int `xorm:"NOT NULL DEFAULT 0"` | |||||
//user | |||||
RegistDate timeutil.TimeStamp `xorm:"NOT NULL"` | |||||
//user | |||||
Email string `xorm:"NOT NULL"` | |||||
//user | |||||
Name string `xorm:"NOT NULL"` | |||||
} | |||||
func CountData(wikiCountMap map[string]int) { | |||||
log.Info("start to count other user info data") | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
sess.Select("`user`.*").Table("user") | |||||
userList := make([]*User, 0) | |||||
sess.Find(&userList) | |||||
currentTimeNow := time.Now() | |||||
log.Info("current time:" + currentTimeNow.Format("2006-01-02 15:04:05")) | |||||
yesterday := currentTimeNow.AddDate(0, 0, -1) | |||||
startTime := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, yesterday.Location()) | |||||
start_unix := startTime.Unix() | |||||
log.Info("DB query time:" + startTime.Format("2006-01-02 15:04:05")) | |||||
endTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) | |||||
end_unix := endTime.Unix() | |||||
CountDate := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 1, 0, 0, currentTimeNow.Location()) | |||||
CodeMergeCountMap := queryAction(start_unix, end_unix, 11) | |||||
CommitCountMap := queryAction(start_unix, end_unix, 5) | |||||
IssueCountMap := queryAction(start_unix, end_unix, 10) | |||||
CommentCountMap := queryComment(start_unix, end_unix) | |||||
FocusRepoCountMap := queryWatch(start_unix, end_unix) | |||||
StarRepoCountMap := queryStar(start_unix, end_unix) | |||||
WatchedCountMap := queryFollow(start_unix, end_unix) | |||||
CommitCodeSizeMap, err := GetAllUserKPIStats() | |||||
if err != nil { | |||||
log.Info("query commit code errr.") | |||||
} else { | |||||
log.Info("query commit code size, len=" + fmt.Sprint(len(CommitCodeSizeMap))) | |||||
} | |||||
CommitDatasetSizeMap := queryDatasetSize(start_unix, end_unix) | |||||
SolveIssueCountMap := querySolveIssue(start_unix, end_unix) | |||||
for i, userRecord := range userList { | |||||
var dateRecord UserBusinessAnalysis | |||||
dateRecord.ID = userRecord.ID | |||||
log.Info("i=" + fmt.Sprint(i) + " userName=" + userRecord.Name) | |||||
dateRecord.CountDate = CountDate.Unix() | |||||
dateRecord.Email = userRecord.Email | |||||
dateRecord.RegistDate = userRecord.CreatedUnix | |||||
dateRecord.Name = userRecord.Name | |||||
dateRecord.GiteaAgeMonth = subMonth(currentTimeNow, userRecord.CreatedUnix.AsTime()) | |||||
if _, ok := CodeMergeCountMap[dateRecord.ID]; !ok { | |||||
dateRecord.CodeMergeCount = 0 | |||||
} else { | |||||
dateRecord.CodeMergeCount = CodeMergeCountMap[dateRecord.ID] | |||||
} | |||||
if _, ok := CommitCountMap[dateRecord.ID]; !ok { | |||||
dateRecord.CommitCount = 0 | |||||
} else { | |||||
dateRecord.CommitCount = CommitCountMap[dateRecord.ID] | |||||
} | |||||
if _, ok := IssueCountMap[dateRecord.ID]; !ok { | |||||
dateRecord.IssueCount = 0 | |||||
} else { | |||||
dateRecord.IssueCount = IssueCountMap[dateRecord.ID] | |||||
} | |||||
if _, ok := CommentCountMap[dateRecord.ID]; !ok { | |||||
dateRecord.CommentCount = 0 | |||||
} else { | |||||
dateRecord.CommentCount = CommentCountMap[dateRecord.ID] | |||||
} | |||||
if _, ok := FocusRepoCountMap[dateRecord.ID]; !ok { | |||||
dateRecord.FocusRepoCount = 0 | |||||
} else { | |||||
dateRecord.FocusRepoCount = FocusRepoCountMap[dateRecord.ID] | |||||
} | |||||
if _, ok := StarRepoCountMap[dateRecord.ID]; !ok { | |||||
dateRecord.StarRepoCount = 0 | |||||
} else { | |||||
dateRecord.StarRepoCount = StarRepoCountMap[dateRecord.ID] | |||||
} | |||||
if _, ok := WatchedCountMap[dateRecord.ID]; !ok { | |||||
dateRecord.WatchedCount = 0 | |||||
} else { | |||||
dateRecord.WatchedCount = WatchedCountMap[dateRecord.ID] | |||||
} | |||||
if _, ok := CommitCodeSizeMap[dateRecord.Email]; !ok { | |||||
dateRecord.CommitCodeSize = 0 | |||||
} else { | |||||
dateRecord.CommitCodeSize = int(CommitCodeSizeMap[dateRecord.Email].CommitLines) | |||||
} | |||||
if _, ok := CommitDatasetSizeMap[dateRecord.ID]; !ok { | |||||
dateRecord.CommitDatasetSize = 0 | |||||
} else { | |||||
dateRecord.CommitDatasetSize = CommitDatasetSizeMap[dateRecord.ID] | |||||
} | |||||
if _, ok := SolveIssueCountMap[dateRecord.ID]; !ok { | |||||
dateRecord.SolveIssueCount = 0 | |||||
} else { | |||||
dateRecord.SolveIssueCount = SolveIssueCountMap[dateRecord.ID] | |||||
} | |||||
if _, ok := wikiCountMap[dateRecord.Name]; !ok { | |||||
dateRecord.EncyclopediasCount = 0 | |||||
} else { | |||||
dateRecord.EncyclopediasCount = wikiCountMap[dateRecord.Name] | |||||
} | |||||
dateRecord.CommitModelCount = 0 | |||||
statictisSess := xStatistic.NewSession() | |||||
defer statictisSess.Close() | |||||
statictisSess.Insert(&dateRecord) | |||||
} | |||||
} | |||||
func querySolveIssue(start_unix int64, end_unix int64) map[int64]int { | |||||
//select issue_assignees.* from issue_assignees,issue where issue.is_closed=true and issue.id=issue_assignees.issue_id | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
sess.Select("issue_assignees.*").Table("issue_assignees"). | |||||
Join("inner", "issue", "issue.id=issue_assignees.issue_id"). | |||||
Where("issue.is_closed=true and issue.closed_unix>=" + fmt.Sprint(start_unix) + " and issue.closed_unix<=" + fmt.Sprint(end_unix)) | |||||
issueAssigneesList := make([]*IssueAssignees, 0) | |||||
sess.Find(&issueAssigneesList) | |||||
resultMap := make(map[int64]int) | |||||
log.Info("query IssueAssignees size=" + fmt.Sprint(len(issueAssigneesList))) | |||||
for _, issueAssigneesRecord := range issueAssigneesList { | |||||
if _, ok := resultMap[issueAssigneesRecord.AssigneeID]; !ok { | |||||
resultMap[issueAssigneesRecord.AssigneeID] = 1 | |||||
} else { | |||||
resultMap[issueAssigneesRecord.AssigneeID] += 1 | |||||
} | |||||
} | |||||
return resultMap | |||||
} | |||||
func queryAction(start_unix int64, end_unix int64, actionType int64) map[int64]int { | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
sess.Select("id,user_id,op_type,act_user_id").Table("action").Where("op_type=" + fmt.Sprint(actionType) + " and created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) | |||||
actionList := make([]*Action, 0) | |||||
sess.Find(&actionList) | |||||
resultMap := make(map[int64]int) | |||||
log.Info("query action size=" + fmt.Sprint(len(actionList))) | |||||
for _, actionRecord := range actionList { | |||||
if _, ok := resultMap[actionRecord.UserID]; !ok { | |||||
resultMap[actionRecord.UserID] = 1 | |||||
} else { | |||||
resultMap[actionRecord.UserID] += 1 | |||||
} | |||||
} | |||||
return resultMap | |||||
} | |||||
func queryComment(start_unix int64, end_unix int64) map[int64]int { | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
sess.Select("id,type,poster_id").Table("comment").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) | |||||
commentList := make([]*Comment, 0) | |||||
sess.Find(&commentList) | |||||
resultMap := make(map[int64]int) | |||||
log.Info("query Comment size=" + fmt.Sprint(len(commentList))) | |||||
for _, commentRecord := range commentList { | |||||
if _, ok := resultMap[commentRecord.PosterID]; !ok { | |||||
resultMap[commentRecord.PosterID] = 1 | |||||
} else { | |||||
resultMap[commentRecord.PosterID] += 1 | |||||
} | |||||
} | |||||
return resultMap | |||||
} | |||||
func queryWatch(start_unix int64, end_unix int64) map[int64]int { | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
sess.Select("id,user_id,repo_id").Table("watch").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) | |||||
watchList := make([]*Watch, 0) | |||||
sess.Find(&watchList) | |||||
resultMap := make(map[int64]int) | |||||
log.Info("query Watch size=" + fmt.Sprint(len(watchList))) | |||||
for _, watchRecord := range watchList { | |||||
if _, ok := resultMap[watchRecord.UserID]; !ok { | |||||
resultMap[watchRecord.UserID] = 1 | |||||
} else { | |||||
resultMap[watchRecord.UserID] += 1 | |||||
} | |||||
} | |||||
return resultMap | |||||
} | |||||
func queryStar(start_unix int64, end_unix int64) map[int64]int { | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
sess.Select("id,uid,repo_id").Table("star").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) | |||||
starList := make([]*Star, 0) | |||||
sess.Find(&starList) | |||||
resultMap := make(map[int64]int) | |||||
log.Info("query Star size=" + fmt.Sprint(len(starList))) | |||||
for _, starRecord := range starList { | |||||
if _, ok := resultMap[starRecord.UID]; !ok { | |||||
resultMap[starRecord.UID] = 1 | |||||
} else { | |||||
resultMap[starRecord.UID] += 1 | |||||
} | |||||
} | |||||
return resultMap | |||||
} | |||||
func queryFollow(start_unix int64, end_unix int64) map[int64]int { | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
sess.Select("id,user_id,follow_id").Table("follow").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) | |||||
followList := make([]*Follow, 0) | |||||
sess.Find(&followList) | |||||
resultMap := make(map[int64]int) | |||||
log.Info("query Follow size=" + fmt.Sprint(len(followList))) | |||||
for _, followRecord := range followList { | |||||
if _, ok := resultMap[followRecord.UserID]; !ok { | |||||
resultMap[followRecord.UserID] = 1 | |||||
} else { | |||||
resultMap[followRecord.UserID] += 1 | |||||
} | |||||
} | |||||
return resultMap | |||||
} | |||||
func queryDatasetSize(start_unix int64, end_unix int64) map[int64]int { | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
sess.Select("id,uploader_id,size").Table("attachment").Where(" created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix)) | |||||
attachmentList := make([]*Attachment, 0) | |||||
sess.Find(&attachmentList) | |||||
resultMap := make(map[int64]int) | |||||
log.Info("query Attachment size=" + fmt.Sprint(len(attachmentList))) | |||||
for _, attachRecord := range attachmentList { | |||||
if _, ok := resultMap[attachRecord.UploaderID]; !ok { | |||||
resultMap[attachRecord.UploaderID] = int(attachRecord.Size / (1024 * 1024)) //MB | |||||
} else { | |||||
resultMap[attachRecord.UploaderID] += int(attachRecord.Size / (1024 * 1024)) //MB | |||||
} | |||||
} | |||||
return resultMap | |||||
} | |||||
func subMonth(t1, t2 time.Time) (month int) { | |||||
y1 := t1.Year() | |||||
y2 := t2.Year() | |||||
m1 := int(t1.Month()) | |||||
m2 := int(t2.Month()) | |||||
d1 := t1.Day() | |||||
d2 := t2.Day() | |||||
yearInterval := y1 - y2 | |||||
// 如果 d1的 月-日 小于 d2的 月-日 那么 yearInterval-- 这样就得到了相差的年数 | |||||
if m1 < m2 || m1 == m2 && d1 < d2 { | |||||
yearInterval-- | |||||
} | |||||
// 获取月数差值 | |||||
monthInterval := (m1 + 12) - m2 | |||||
if d1 < d2 { | |||||
monthInterval-- | |||||
} | |||||
monthInterval %= 12 | |||||
month = yearInterval*12 + monthInterval | |||||
return month | |||||
} |
@@ -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. | ||||
@@ -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 | ||||
@@ -13,6 +13,7 @@ import ( | |||||
type RepoKPIStats struct { | type RepoKPIStats struct { | ||||
Contributors int64 | Contributors int64 | ||||
KeyContributors int64 | KeyContributors int64 | ||||
DevelopAge int64 | |||||
ContributorsAdded int64 | ContributorsAdded int64 | ||||
CommitsAdded int64 | CommitsAdded int64 | ||||
CommitLinesModified int64 | CommitLinesModified int64 | ||||
@@ -66,6 +67,10 @@ func GetRepoKPIStats(repoPath string) (*RepoKPIStats, error) { | |||||
} | } | ||||
err = setDevelopAge(repoPath, stats) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("FillFromGit: %v", err) | |||||
} | |||||
err = setRepoKPIStats(repoPath, fourMonthAgo, stats, newContributersDict) | err = setRepoKPIStats(repoPath, fourMonthAgo, stats, newContributersDict) | ||||
if err != nil { | if err != nil { | ||||
@@ -75,6 +80,27 @@ func GetRepoKPIStats(repoPath string) (*RepoKPIStats, error) { | |||||
} | } | ||||
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) { | func GetUserKPIStats(repoPath string) (map[string]*UserKPIStats, error) { | ||||
timeUntil := time.Now() | timeUntil := time.Now() | ||||
@@ -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,7 @@ 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 | |||||
template.items = Template Items | template.items = Template Items | ||||
template.git_content = Git Content (Default Branch) | template.git_content = Git Content (Default Branch) | ||||
@@ -772,6 +772,7 @@ cloudbrain_selection=云脑选择 | |||||
cloudbrain_platform_selection=选择您准备使用的云脑平台: | cloudbrain_platform_selection=选择您准备使用的云脑平台: | ||||
confirm_choice=确定 | confirm_choice=确定 | ||||
cloudbran1_tips=只有zip格式的数据集才能发起云脑任务 | cloudbran1_tips=只有zip格式的数据集才能发起云脑任务 | ||||
cloudbrain_creator=创建者 | |||||
template.items=模板选项 | template.items=模板选项 | ||||
template.git_content=Git数据(默认分支) | template.git_content=Git数据(默认分支) | ||||
@@ -69,7 +69,8 @@ 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) { | |||||
log.Info("", task.User.Name) | |||||
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 | ||||
@@ -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 TimeingCountData() { | |||||
//query wiki data | |||||
log.Info("start to time count data") | |||||
wikiMap := make(map[string]int) | |||||
currentTimeNow := time.Now() | |||||
log.Info("current time:" + currentTimeNow.Format("2006-01-02 15:04:05")) | |||||
yesterday := currentTimeNow.AddDate(0, 0, -1) | |||||
repoList, 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) | |||||
} |
@@ -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) { | ||||
@@ -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> | ||||
@@ -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> | ||||
@@ -262,33 +262,46 @@ | |||||
<div class="row"> | <div class="row"> | ||||
<!-- 任务名 --> | <!-- 任务名 --> | ||||
<div class="six wide column"> | |||||
<a class="title" href="{{$.Link}}/{{.JobID}}"> | |||||
<div class="five wide column"> | |||||
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}"> | |||||
<span class="fitted">{{svg "octicon-tasklist" 16}}</span> | <span class="fitted">{{svg "octicon-tasklist" 16}}</span> | ||||
<span class="fitted">{{.JobName}}</span> | |||||
<span class="fitted text_over" style="width: 90%;">{{.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 style="display:flex;position: relative; justify-content: flex-start;"><i class="{{.Status}}"></i><span style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span> | |||||
</span> | |||||
<!-- <span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" > | |||||
{{if eq .Status "STOPPED"}} | |||||
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-stop"></i><span style="margin-left: 0.4em;font-size: 12px;">已停止</span></span> | |||||
{{else if eq .Status "RUNNING"}} | |||||
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-running"></i><span style="margin-left: 0.4em;font-size: 12px;">运行中</span></span> | |||||
{{else if eq .Status "FAILED"}} | |||||
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-running"></i><span style="margin-left: 0.4em;font-size: 12px;">运行失败</span></span> | |||||
{{else if eq .Status "WAITING"}} | |||||
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="showCircle"></i><span style="margin-left: 0.4em;font-size: 12px;">初始化等待</span></span> | |||||
{{end}} | |||||
</span> --> | </span> --> | ||||
{{if eq .Status "STOPPED"}} | |||||
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-stop"></i><span style="margin-left: 0.4em;font-size: 12px;">已停止</span></span> | |||||
{{else if eq .Status "RUNNING"}} | |||||
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-running"></i><span style="margin-left: 0.4em;font-size: 12px;">运行中</span></span> | |||||
{{else if eq .Status "FAILED"}} | |||||
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-running"></i><span style="margin-left: 0.4em;font-size: 12px;">运行失败</span></span> | |||||
{{else if eq .Status "WAITING"}} | |||||
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="showCircle"></i><span style="margin-left: 0.4em;font-size: 12px;">初始化等待</span></span> | |||||
{{end}} | |||||
<!-- 任务创建时间 --> | <!-- 任务创建时间 --> | ||||
<span class="time-show">{{TimeSinceUnix .CreatedUnix $.Lang}}</span> | |||||
<span class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span> | |||||
</div> | </div> | ||||
<div class="seven wide column text right"> | |||||
<div class="two wide column"> | |||||
<span style="display: block;">{{$.i18n.Tr "repo.cloudbrain_creator"}}</span><span class="text_over" title="{{.User.Name}}">{{.User.Name}}</span> | |||||
</div> | |||||
<div class="six wide column text right"> | |||||
<div class="ui compact buttons" style="margin-right:10px;"> | <div class="ui compact buttons" style="margin-right:10px;"> | ||||
{{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"> | ||||
@@ -452,15 +465,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') { | ||||
@@ -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> | ||||
@@ -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,20 @@ | |||||
{{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 class="ui large breadcrumb"> | |||||
<a href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a> | |||||
<div class="divider"> / </div> | |||||
</div> --> | |||||
{{template "repo/issue/navbar" .}} | |||||
</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"> | ||||
{{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,16 @@ | |||||
{{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;"> | |||||
<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> | ||||
{{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}} | ||||
@@ -262,7 +262,7 @@ | |||||
{{.Status}} | {{.Status}} | ||||
</span> | </span> | ||||
<!-- 任务创建时间 --> | <!-- 任务创建时间 --> | ||||
<span class="">{{TimeSinceUnix .CreatedUnix $.Lang}}</span> | |||||
<span class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span> | |||||
</div> | </div> | ||||
<div class="seven wide column text right"> | <div class="seven wide column text right"> | ||||
@@ -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> | ||||
@@ -224,12 +224,21 @@ footer .column{margin-bottom:0!important; padding-bottom:0!important;} | |||||
// icon cloudbrain | // icon cloudbrain | ||||
.i-round{display:inline-block;width:18px;height:18px;background:url("/img/icons.svg");background-position: -496px -52px;} | .i-round{display:inline-block;width:18px;height:18px;background:url("/img/icons.svg");background-position: -496px -52px;} | ||||
.i-bg-organ{background-position: -496px -52px;} | .i-bg-organ{background-position: -496px -52px;} | ||||
.i-bg-stop{background-position: -459px -52px;} | |||||
.i-bg-running{background-position: -478px -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;} | .i-bg-orange{background-position: -495px -51px;} | ||||
.i-bg-red{background-position: -532px -52px;} | |||||
.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-green{background-position: -441px -52px;} | ||||
.i-bg-used{background-position: -514px -52px;} | .i-bg-used{background-position: -514px -52px;} | ||||
.icon-bind{background-position: -550px -52px;} | .icon-bind{background-position: -550px -52px;} | ||||
.icon-unbind{background-position: -568px -52px;} | .icon-unbind{background-position: -568px -52px;} | ||||
.showCircle{display:inline-block;background-image:url('/img/loading.gif');background-repeat:no-repeat;width:16px;height:16px;background-size:16px 16px;margin-right:5px;} | |||||
.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%; | |||||
} | |||||