diff --git a/models/org.go b/models/org.go old mode 100644 new mode 100755 index 58afc5cb5..e8006d55f --- a/models/org.go +++ b/models/org.go @@ -182,6 +182,7 @@ func CreateOrganization(org, owner *User) (err error) { if _, err = sess.Insert(&OrgUser{ UID: owner.ID, OrgID: org.ID, + IsPublic: setting.Service.DefaultOrgMemberVisible, }); err != nil { return fmt.Errorf("insert org-user relation: %v", err) } diff --git a/models/repo.go b/models/repo.go index c8629875e..1a5cf122c 100755 --- a/models/repo.go +++ b/models/repo.go @@ -210,9 +210,12 @@ type Repository struct { Balance string `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"` + // only git clone total count + GitCloneCnt int64 `xorm:"NOT NULL DEFAULT 0"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` @@ -2473,6 +2476,24 @@ func (repo *Repository) IncreaseCloneCnt() { 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 { if _, err := x.Exec("UPDATE `repository` SET num_commit = ? where id = ?", repo.NumCommit, repo.ID); err != nil { return err diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 09d04b4f1..9f31612b6 100755 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -163,6 +163,7 @@ var ( // UI settings UI = struct { ExplorePagingNum int + ContributorPagingNum int IssuePagingNum int RepoSearchPagingNum int MembersPagingNum int @@ -203,19 +204,20 @@ var ( Keywords string } `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 { MinTimeout time.Duration TimeoutStep time.Duration diff --git a/modules/storage/obs.go b/modules/storage/obs.go index bd73281d0..239580adb 100755 --- a/modules/storage/obs.go +++ b/modules/storage/obs.go @@ -10,11 +10,11 @@ import ( "strconv" "strings" - "github.com/unknwon/com" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/obs" "code.gitea.io/gitea/modules/setting" + + "github.com/unknwon/com" ) type FileInfo struct { @@ -178,30 +178,45 @@ func GetObsListObject(jobName, parentDir string) ([]FileInfo, error) { input := &obs.ListObjectsInput{} input.Bucket = setting.Bucket input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/") + strPrefix := strings.Split(input.Prefix, "/") output, err := ObsCli.ListObjects(input) fileInfos := make([]FileInfo, 0) if err == nil { for _, val := range output.Contents { str1 := strings.Split(val.Key, "/") var isDir bool - var fileName,nextParentDir string + var fileName, nextParentDir string if strings.HasSuffix(val.Key, "/") { + //dirs in next level dir + if len(str1)-len(strPrefix) > 2 { + continue + } fileName = str1[len(str1)-2] 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 } } else { + //files in next level dir + if len(str1)-len(strPrefix) > 1 { + continue + } fileName = str1[len(str1)-1] isDir = false + nextParentDir = parentDir } 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, - Size: val.Size, - IsDir:isDir, + Size: val.Size, + IsDir: isDir, ParenDir: nextParentDir, } fileInfos = append(fileInfos, fileInfo) @@ -242,7 +257,7 @@ func GetObsCreateSignedUrl(jobName, parentDir, fileName string) (string, error) input := &obs.CreateSignedUrlInput{} input.Bucket = setting.Bucket input.Key = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir, fileName), "/") - + input.Expires = 60 * 60 input.Method = obs.HttpMethodGet diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index fca4e2850..bc71693b8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -218,6 +218,7 @@ show_only_private = Showing only private show_only_public = Showing only public issues.in_your_repos = In your repositories +contributors = Contributors [explore] repos = Repositories @@ -755,6 +756,7 @@ unit_disabled = The site administrator has disabled this repository section. language_other = Other datasets = Datasets datasets.desc = Enable Dataset +cloudbrain_helper=Use GPU/NPU resources to open notebooks, model training tasks, etc. debug=Debug stop=Stop diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 614e8ad92..a6d40355b 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -220,6 +220,8 @@ show_only_public=只显示公开的 issues.in_your_repos=属于该用户项目的 +contributors=贡献者 + [explore] repos=项目 users=用户 @@ -757,6 +759,7 @@ unit_disabled=站点管理员已禁用此项目单元。 language_other=其它 datasets=数据集 datasets.desc=数据集功能 +cloudbrain_helper=使用GPU/NPU资源,开启Notebook、模型训练任务等 debug=调试 stop=停止 diff --git a/public/img/git-logo.svg b/public/img/git-logo.svg new file mode 100644 index 000000000..70e6ab484 --- /dev/null +++ b/public/img/git-logo.svg @@ -0,0 +1,41 @@ + + + diff --git a/routers/org/members.go b/routers/org/members.go old mode 100644 new mode 100755 diff --git a/routers/repo/http.go b/routers/repo/http.go index ed6276466..ad2abf567 100644 --- a/routers/repo/http.go +++ b/routers/repo/http.go @@ -317,6 +317,12 @@ func HTTP(ctx *context.Context) { go repo.IncreaseCloneCnt() } + if ctx.Req.Method == "POST" { + if strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") { + go repo.IncreaseGitCloneCnt() + } + } + w := ctx.Resp r := ctx.Req.Request cfg := &serviceConfig{ diff --git a/routers/repo/user_data_analysis.go b/routers/repo/user_data_analysis.go index 7dc7af321..68cccd478 100755 --- a/routers/repo/user_data_analysis.go +++ b/routers/repo/user_data_analysis.go @@ -1,13 +1,27 @@ package repo import ( + "fmt" + "net/http" "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "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) { t, _ := time.Parse("2006-01-02", date) diff --git a/routers/repo/view.go b/routers/repo/view.go old mode 100644 new mode 100755 index 9477b27dd..cd59ec920 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -12,6 +12,7 @@ import ( "fmt" gotemplate "html/template" "io/ioutil" + "net/http" "net/url" "path" "strings" @@ -31,11 +32,12 @@ import ( ) 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 { @@ -243,6 +245,11 @@ func renderDirectory(ctx *context.Context, treeLink string) { ctx.Data["ReadmeInList"] = true ctx.Data["ReadmeExist"] = true ctx.Data["FileIsSymlink"] = readmeFile.isSymlink + ctx.Data["ReadmeName"] = readmeFile.name + + if ctx.Repo.CanEnableEditor() { + ctx.Data["CanEditFile"] = true + } dataRc, err := readmeFile.blob.DataAsync() if err != nil { @@ -570,19 +577,29 @@ func safeURL(address string) string { } 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"` +} + +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{ +func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo { for _, c := range contributorInfos { - if strings.Compare(c.Email,email) == 0 { + if strings.Compare(c.Email, email) == 0 { return c } } return nil } + // Home render repository home page func Home(ctx *context.Context) { if len(ctx.Repo.Units) > 0 { @@ -591,35 +608,41 @@ func Home(ctx *context.Context) { if err == nil && contributors != nil { startTime := time.Now() var contributorInfos []*ContributorInfo - contributorInfoHash:= make(map[string]*ContributorInfo) + contributorInfoHash := make(map[string]*ContributorInfo) + count := 0 for _, c := range contributors { - if strings.Compare(c.Email,"") == 0 { + if count >= 25 { + continue + } + 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 { + if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok { // existed: same primary email, different committer name existedContributorInfo.CommitCnt += c.CommitCnt - }else{ + } else { // new committer info 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 } } else { // 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 existedContributorInfo.CommitCnt += c.CommitCnt - }else{ + } else { var newContributor = &ContributorInfo{ - user, c.Email,c.CommitCnt, + user, "", "",c.Email, c.CommitCnt, } + count++ contributorInfos = append(contributorInfos, newContributor) contributorInfoHash[c.Email] = newContributor } @@ -627,7 +650,7 @@ func Home(ctx *context.Context) { } ctx.Data["ContributorInfo"] = contributorInfos 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() { task, err := models.GetMigratingTask(ctx.Repo.Repository.ID) @@ -694,13 +717,13 @@ func renderLicense(ctx *context.Context) { log.Error("failed to get license content: %v, err:%v", f, err) 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 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) { @@ -801,31 +824,31 @@ func renderCode(ctx *context.Context) { baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath()) defer baseGitRepo.Close() if err != nil { - log.Error("error open baseRepo:%s",ctx.Repo.Repository.BaseRepo.RepoPath()) + log.Error("error open baseRepo:%s", ctx.Repo.Repository.BaseRepo.RepoPath()) ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error - }else{ - if _,error:= baseGitRepo.GetBranch(ctx.Repo.BranchName);error==nil{ + } else { + if _, error := baseGitRepo.GetBranch(ctx.Repo.BranchName); error == nil { //base repo has the same branch, then compare between current repo branch and base repo's branch - compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName - ctx.SetParams("*",compareUrl) + compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName + ctx.SetParams("*", compareUrl) ctx.Data["UpstreamSameBranchName"] = true - }else{ + } else { //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 } _, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx) 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() - }else{ + } else { log.Info("compareInfo nothing different") ctx.Data["FetchUpstreamCnt"] = 0 } - }else{ + } else { ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error } } @@ -893,3 +916,64 @@ func Forks(ctx *context.Context) { 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, + }) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 7e7d0642a..383a308b6 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -311,7 +311,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Head("/", func() string { return "" }) - m.Get("/", routers.Dashboard) + m.Get("/", routers.Home) m.Get("/dashboard", routers.Dashboard) m.Group("/explore", func() { m.Get("", func(ctx *context.Context) { @@ -615,6 +615,11 @@ func RegisterRoutes(m *macaron.Macaron) { //reqRepoBlockChainWriter := context.RequireRepoWriter(models.UnitTypeBlockChain) // ***** START: Organization ***** + m.Group("/org", func() { + m.Group("/:org", func() { + m.Get("/members", org.Members) + }, context.OrgAssignment()) + }) m.Group("/org", func() { m.Group("", func() { m.Get("/create", org.Create) @@ -625,7 +630,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/dashboard", user.Dashboard) m.Get("/^:type(issues|pulls)$", user.Issues) m.Get("/milestones", reqMilestonesDashboardPageEnabled, user.Milestones) - m.Get("/members", org.Members) + //m.Get("/members", org.Members) m.Post("/members/action/:action", org.MembersAction) m.Get("/teams", org.Teams) @@ -786,9 +791,11 @@ func RegisterRoutes(m *macaron.Macaron) { }, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef()) 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 m.Group("/:username/:reponame", func() { + m.Get("/contributors", repo.Contributors) + m.Get("/contributors/list", repo.ContributorsAPI) m.Group("/milestone", func() { m.Get("/:id", repo.MilestoneIssuesAndPulls) }, reqRepoIssuesOrPullsReader, context.RepoRef()) diff --git a/routers/user/auth.go b/routers/user/auth.go index 44c5ad97d..16af84b66 100755 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -544,7 +544,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR if err := models.UpdateUserCols(u, "language"); err != nil { log.Error(fmt.Sprintf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language)) - return setting.AppSubURL + "/" + return setting.AppSubURL + "/dashboard" } } else { // Language setting of the user use the one previously set @@ -562,7 +562,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR u.SetLastLogin() if err := models.UpdateUserCols(u, "last_login_unix"); err != nil { ctx.ServerError("UpdateUserCols", err) - return setting.AppSubURL + "/" + return setting.AppSubURL + "/dashboard" } if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) { @@ -574,9 +574,9 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR } if obeyRedirect { - ctx.Redirect(setting.AppSubURL + "/") + ctx.Redirect(setting.AppSubURL + "/dashboard") } - return setting.AppSubURL + "/" + return setting.AppSubURL + "/dashboard" } // SignInOAuth handles the OAuth2 login buttons diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 24389d122..110200894 100755 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -180,8 +180,8 @@ var _hmt = _hmt || []; (function() { 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); })(); diff --git a/templates/base/head_home.tmpl b/templates/base/head_home.tmpl index 69bf23f51..d1b2934ea 100644 --- a/templates/base/head_home.tmpl +++ b/templates/base/head_home.tmpl @@ -181,8 +181,8 @@ var _hmt = _hmt || []; (function() { 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); })(); diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index c22cb9fa7..57df408c9 100755 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -7,6 +7,14 @@ +
+