diff --git a/models/cloudbrain.go b/models/cloudbrain.go index 83864bff8..e8fcb75b5 100755 --- a/models/cloudbrain.go +++ b/models/cloudbrain.go @@ -182,14 +182,14 @@ type Cloudbrain struct { AiCenter string //grampus ai center: center_id+center_name TrainUrl string //输出模型的obs路径 - BranchName string //分支名称 + BranchName string `xorm:"varchar(2550)"` //分支名称 Parameters string //传给modelarts的param参数 - BootFile string //启动文件 + BootFile string `xorm:"varchar(2550)"` //启动文件 DataUrl string `xorm:"varchar(3500)"` //数据集的obs路径 LogUrl string //日志输出的obs路径 PreVersionId int64 //父版本的版本id FlavorCode string //modelarts上的规格id - Description string `xorm:"varchar(256)"` //描述 + Description string `xorm:"varchar(2550)"` //描述 WorkServerNumber int //节点数 FlavorName string //规格名称 EngineName string //引擎名称 @@ -458,29 +458,32 @@ type GetImagesPayload struct { type CloudbrainsOptions struct { ListOptions - RepoID int64 // include all repos if empty - UserID int64 - JobID string - SortType string - CloudbrainIDs []int64 - JobStatus []string - JobStatusNot bool - Keyword string - Type int - JobTypes []string - VersionName string - IsLatestVersion string - JobTypeNot bool - NeedRepoInfo bool - RepoIDList []int64 - BeginTime time.Time - EndTime time.Time - ComputeResource string - BeginTimeUnix int64 - EndTimeUnix int64 - AiCenter string - NeedDeleteInfo string - Cluster string + RepoID int64 // include all repos if empty + UserID int64 + JobID string + SortType string + CloudbrainIDs []int64 + JobStatus []string + JobStatusNot bool + Keyword string + Type int + JobTypes []string + VersionName string + IsLatestVersion string + JobTypeNot bool + NeedRepoInfo bool + RepoIDList []int64 + BeginTime time.Time + EndTime time.Time + ComputeResource string + BeginTimeUnix int64 + EndTimeUnix int64 + AiCenter string + NeedDeleteInfo string + Cluster string + AccCardType string + AccCardsNum int + WorkServerNumber int } type TaskPod struct { @@ -1568,7 +1571,8 @@ type CreateGrampusJobResponse struct { type GetGrampusJobResponse struct { GrampusResult - JobInfo GrampusJobInfo `json:"otJob"` + JobInfo GrampusJobInfo `json:"otJob"` + ExitDiagnostics string `json:"exitDiagnostics"` } type GrampusNotebookResponse struct { @@ -2440,18 +2444,44 @@ func CloudbrainAll(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { ) } + if opts.WorkServerNumber > 0 { + if opts.WorkServerNumber == 1 { + cond = cond.And(builder.Or( + builder.Eq{"cloudbrain.work_server_number": 0}, + builder.Eq{"cloudbrain.work_server_number": 1}, + builder.IsNull{"cloudbrain.work_server_number"}, + )) + } else { + cond = cond.And( + builder.Eq{"cloudbrain.work_server_number": opts.WorkServerNumber}, + ) + } + } + + if opts.AccCardType != "" { + cond = cond.And(builder.Eq{"cloudbrain_spec.acc_card_type": opts.AccCardType}) + } + if opts.AccCardsNum >= 0 { + cond = cond.And(builder.Eq{"cloudbrain_spec.acc_cards_num": opts.AccCardsNum}) + } + var count int64 var err error condition := "cloudbrain.user_id = `user`.id" if len(opts.Keyword) == 0 { - count, err = sess.Unscoped().Where(cond).Count(new(Cloudbrain)) + count, err = sess.Table(&Cloudbrain{}).Unscoped().Where(cond). + Join("left", "`user`", condition). + Join("left", "cloudbrain_spec", "cloudbrain.id = cloudbrain_spec.cloudbrain_id"). + Count(new(CloudbrainInfo)) } else { lowerKeyWord := strings.ToLower(opts.Keyword) cond = cond.And(builder.Or(builder.Like{"LOWER(cloudbrain.job_name)", lowerKeyWord}, builder.Like{"LOWER(cloudbrain.display_job_name)", lowerKeyWord}, builder.Like{"`user`.lower_name", lowerKeyWord})) count, err = sess.Table(&Cloudbrain{}).Unscoped().Where(cond). - Join("left", "`user`", condition).Count(new(CloudbrainInfo)) + Join("left", "`user`", condition). + Join("left", "cloudbrain_spec", "cloudbrain.id = cloudbrain_spec.cloudbrain_id"). + Count(new(CloudbrainInfo)) } @@ -2473,6 +2503,7 @@ func CloudbrainAll(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { cloudbrains := make([]*CloudbrainInfo, 0, setting.UI.IssuePagingNum) if err := sess.Table(&Cloudbrain{}).Unscoped().Where(cond). Join("left", "`user`", condition). + Join("left", "cloudbrain_spec", "cloudbrain.id = cloudbrain_spec.cloudbrain_id"). Find(&cloudbrains); err != nil { return nil, 0, fmt.Errorf("Find: %v", err) } diff --git a/modules/grampus/resty.go b/modules/grampus/resty.go index a0d5384e2..3611240b9 100755 --- a/modules/grampus/resty.go +++ b/modules/grampus/resty.go @@ -198,7 +198,6 @@ sendjob: SetAuthToken(TOKEN). SetResult(&result). Get(HOST + urlTrainJob + "/" + jobID) - if err != nil { return nil, fmt.Errorf("resty GetJob: %v", err) } diff --git a/modules/modelarts/modelarts.go b/modules/modelarts/modelarts.go index bd7f848fc..7f67e36f4 100755 --- a/modules/modelarts/modelarts.go +++ b/modules/modelarts/modelarts.go @@ -22,9 +22,9 @@ import ( const ( //notebook - storageTypeOBS = "obs" - autoStopDuration = 4 * 60 * 60 - AutoStopDurationMs = 4 * 60 * 60 * 1000 + storageTypeOBS = "obs" + autoStopDuration = 4 * 60 * 60 + AutoStopDurationMs = 4 * 60 * 60 * 1000 CodePath = "/code/" OutputPath = "/output/" @@ -168,7 +168,6 @@ type OrgMultiNode struct { Node []int `json:"node"` } - type Parameters struct { Parameter []struct { Label string `json:"label"` diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 3268cce60..43427893f 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -139,6 +139,9 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt } repo.IsMirror = true + if repo.Description == "" { + repo.Description = opts.Description + } err = models.UpdateRepository(repo, false) } else { repo, err = CleanUpMigrateInfo(repo) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 411aa8ee7..199a38911 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -406,6 +406,8 @@ sspi_auth_failed = SSPI authentication failed change_email = Change email change_email_address = Change email address new_email_address = New email address +openi_community_really_awesome = OpenI, Really Awesome! + [phone] format_err=The format of phone number is wrong. query_err=Fail to query phone number, please try again later. @@ -1061,6 +1063,8 @@ model_rename=Duplicate model name, please modify model name. notebook_file_not_exist=Notebook file does not exist. notebook_select_wrong=Please select a Notebook(.ipynb) file first. +notebook_path_too_long=The total length of selected file or files path exceed 255 characters, please select a shorter path file or change the file path. +notebook_branch_name_too_long=The total length of branch or branches name exceed 255 characters, please select a file in other branch. notebook_file_no_right=You have no right to access the Notebook(.ipynb) file. notebook_repo_conflict=The files in different branches of the same repository can not run together. debug_again_fail=Fail to restart debug task, please try again later. @@ -1261,6 +1265,13 @@ modelarts.fullscreen_log_file = View in full screen modelarts.exit_full_screen = Exit fullscreen modelarts.no_node_right = The value of 'Amount of Compute Node' is wrong, you have no right to use the current value of 'Amount of Compute Node'. +scrolled_logs_top = You have scrolled to the top of the log +scrolled_logs_top_pls_retry = You have scrolled to the top of the log, please try again later! +scrolled_logs_bottom = You have scrolled to the bottom of the log +scrolled_logs_bottom_pls_retry = You have scrolled to the bottom of the log, please try again later! + +canceled_operation = You have canceled the operation +successfully_deleted = Successfully deleted debug_task_not_created = Debug task has not been created train_task_not_created = Train task has not been created @@ -1474,7 +1485,7 @@ blame = Blame normal_view = Normal View line = line lines = lines -notebook_open = Open in Notebook +notebook_open = Run Online editor.new_file = New File editor.upload_file = Upload File diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 5a400fd91..d7bdd2454 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -409,6 +409,8 @@ sspi_auth_failed=SSPI 认证失败 change_email=修改邮箱 change_email_address=修改邮箱地址 new_email_address=新邮箱地址 +openi_community_really_awesome=启智社区 确实给力 + [phone] format_err=手机号格式错误。 query_err=查询手机号失败,请稍后再试。 @@ -1060,6 +1062,8 @@ model_rename=模型名称重复,请修改模型名称 notebook_file_not_exist=Notebook文件不存在。 notebook_select_wrong=请先选择Notebook(.ipynb)文件。 +notebook_path_too_long=选择的一个或多个Notebook文件路径总长度超过255个字符,请选择路径较短的文件或调整文件路径。 +notebook_branch_name_too_long=选择的一个或多个Notebook文件分支名总长度超过255个字符,请选择其他分支的文件。 notebook_file_no_right=您没有这个Notebook文件的读权限。 notebook_repo_conflict=同一个仓库的不同分支文件不能同时运行。 debug_again_fail=再次调试失败,请稍后再试。 @@ -1273,6 +1277,13 @@ modelarts.fullscreen_log_file=全屏查看 modelarts.exit_full_screen=退出全屏 modelarts.no_node_right = 计算节点数的值配置错误,您没有权限使用当前配置的计算节点数。 +scrolled_logs_top = 您已翻阅至日志顶部 +scrolled_logs_top_pls_retry = 您已翻阅至日志顶部,请稍后再试! +scrolled_logs_bottom = 您已翻阅至日志底部 +scrolled_logs_bottom_pls_retry = 您已翻阅至日志底部,请稍后再试! + +canceled_operation = 您已取消操作 +successfully_deleted = 删除成功 debug_task_not_created = 未创建过调试任务 train_task_not_created = 未创建过训练任务 @@ -1492,7 +1503,7 @@ normal_view=普通视图 line=行 lines=行 -notebook_open = 在Notebook中打开 +notebook_open = 在线运行 editor.new_file=新建文件 editor.upload_file=上传文件 diff --git a/public/img/login_bg_default.png b/public/img/login_bg_default.png new file mode 100644 index 000000000..0c80142c9 Binary files /dev/null and b/public/img/login_bg_default.png differ diff --git a/routers/api/v1/repo/cloudbrain_dashboard.go b/routers/api/v1/repo/cloudbrain_dashboard.go index 4710bf974..1fbef897b 100755 --- a/routers/api/v1/repo/cloudbrain_dashboard.go +++ b/routers/api/v1/repo/cloudbrain_dashboard.go @@ -645,7 +645,7 @@ func GetAllCloudbrainsPeriodDistribution(ctx *context.Context) { } } - ComputeResourceList := []string{"CPU/GPU", "NPU"} + ComputeResourceList := []string{"CPU/GPU", "NPU", "GCU"} for _, v := range ComputeResourceList { if _, ok := cloudBrainComputeResource[v]; !ok { cloudBrainComputeResource[v] = 0 @@ -687,7 +687,6 @@ func GetCloudbrainsDetailData(ctx *context.Context) { return } recordBeginTime := recordCloudbrain[0].Cloudbrain.CreatedUnix - endTime := time.Now() listType := ctx.Query("listType") jobType := ctx.Query("jobType") jobStatus := ctx.Query("jobStatus") @@ -695,6 +694,33 @@ func GetCloudbrainsDetailData(ctx *context.Context) { aiCenter := ctx.Query("aiCenter") needDeleteInfo := ctx.Query("needDeleteInfo") + accCardType := ctx.Query("accCardType") + accCardsNum := ctx.QueryInt("accCardsNum") + workServerNumber := ctx.QueryInt("workServerNumber") + beginTimeStr := ctx.QueryTrim("beginTime") + endTimeStr := ctx.QueryTrim("endTime") + var beginTimeUnix int64 + var endTimeUnix int64 + if beginTimeStr == "" || endTimeStr == "" { + beginTimeUnix = int64(recordBeginTime) + endTimeUnix = time.Now().Unix() + } else { + beginTime, err := time.ParseInLocation("2006-01-02T15:04:05", beginTimeStr, time.Local) + if err != nil { + log.Error("Can not ParseInLocation.", err) + ctx.Error(http.StatusBadRequest, ctx.Tr("ParseInLocation_get_error")) + return + } + beginTimeUnix = beginTime.Unix() + endTime, err := time.ParseInLocation("2006-01-02T15:04:05", endTimeStr, time.Local) + if err != nil { + log.Error("Can not ParseInLocation.", err) + ctx.Error(http.StatusBadRequest, ctx.Tr("ParseInLocation_get_error")) + return + } + endTimeUnix = endTime.Unix() + } + if cloudBrainType == models.TypeCloudBrainOne && aiCenter == models.AICenterOfCloudBrainOne { aiCenter = "" } @@ -753,18 +779,21 @@ func GetCloudbrainsDetailData(ctx *context.Context) { Page: page, PageSize: pageSize, }, - Keyword: keyword, - Type: cloudBrainType, - ComputeResource: listType, - JobTypeNot: jobTypeNot, - JobStatusNot: jobStatusNot, - JobStatus: jobStatuses, - JobTypes: jobTypes, - NeedRepoInfo: true, - BeginTimeUnix: int64(recordBeginTime), - EndTimeUnix: endTime.Unix(), - AiCenter: aiCenter, - NeedDeleteInfo: needDeleteInfo, + Keyword: keyword, + Type: cloudBrainType, + ComputeResource: listType, + JobTypeNot: jobTypeNot, + JobStatusNot: jobStatusNot, + JobStatus: jobStatuses, + JobTypes: jobTypes, + NeedRepoInfo: true, + BeginTimeUnix: beginTimeUnix, + EndTimeUnix: endTimeUnix, + AiCenter: aiCenter, + NeedDeleteInfo: needDeleteInfo, + AccCardType: accCardType, + AccCardsNum: accCardsNum, + WorkServerNumber: workServerNumber, }) if err != nil { ctx.ServerError("Get job failed:", err) @@ -1039,7 +1068,7 @@ func getCloudbrainCount(beginTime time.Time, endTime time.Time, cloudbrains []*m } } - ComputeResourceList := []string{"CPU/GPU", "NPU"} + ComputeResourceList := []string{"CPU/GPU", "NPU", "GCU"} for _, v := range ComputeResourceList { if _, ok := cloudBrainComputeResource[v]; !ok { cloudBrainComputeResource[v] = 0 diff --git a/routers/repo/grampus.go b/routers/repo/grampus.go index 762c9e706..f82f332e9 100755 --- a/routers/repo/grampus.go +++ b/routers/repo/grampus.go @@ -1381,6 +1381,23 @@ func GrampusGetLog(ctx *context.Context) { }) return } + result, err := grampus.GetJob(jobID) + if err != nil { + log.Error("GetJob(%s) failed:%v", job.JobName, err) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "JobName": job.JobName, + "Content": content, + "CanLogDownload": false, + }) + return + } + if result != nil { + job.Status = grampus.TransTrainJobStatus(result.JobInfo.Status) + if job.Status == models.GrampusStatusFailed { + content = content + "\n" + result.ExitDiagnostics + } + } + canLogDownload := err == nil && job.IsUserHasRight(ctx.User) ctx.JSON(http.StatusOK, map[string]interface{}{ "JobName": job.JobName, diff --git a/routers/repo/modelarts.go b/routers/repo/modelarts.go index e0b9cd1b6..84fcecb52 100755 --- a/routers/repo/modelarts.go +++ b/routers/repo/modelarts.go @@ -483,7 +483,7 @@ func getFileUrl(url string, filename string) string { } } - return url + middle + filename + return url + middle + filename + "?reset" } func NotebookRestart(ctx *context.Context) { diff --git a/routers/user/auth.go b/routers/user/auth.go index 5314571d2..ae2c26f33 100755 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -36,6 +36,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/externalaccount" "code.gitea.io/gitea/services/mailer" + "code.gitea.io/gitea/services/repository" "gitea.com/macaron/captcha" "github.com/markbates/goth" @@ -145,6 +146,11 @@ func checkAutoLogin(ctx *context.Context) bool { return false } +func getActivityTpl() string { + result, _ := repository.RecommendContentFromPromote(setting.RecommentRepoAddr + "/signin/activity_tpl") + return result +} + // SignIn render sign in page func SignIn(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("sign_in") @@ -168,6 +174,7 @@ func SignIn(ctx *context.Context) { ctx.Data["PageIsLogin"] = true ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() ctx.Data["EnableCloudBrain"] = true + ctx.Data["ActivityTpl"] = getActivityTpl() ctx.HTML(200, tplSignIn) } @@ -185,6 +192,7 @@ func SignInCloudBrain(ctx *context.Context) { ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsCloudBrainLogin"] = true ctx.Data["EnableCloudBrain"] = true + ctx.Data["ActivityTpl"] = getActivityTpl() ctx.HTML(200, tplSignInCloudBrain) } @@ -197,6 +205,7 @@ func SignInPhone(ctx *context.Context) { } ctx.Data["PageIsPhoneLogin"] = true + ctx.Data["ActivityTpl"] = getActivityTpl() ctx.HTML(200, tplSignInPhone) } @@ -206,6 +215,7 @@ func SignInPhonePost(ctx *context.Context, form auth.PhoneNumberCodeForm) { ctx.Data["PageIsPhoneLogin"] = true ctx.Data["IsCourse"] = ctx.QueryBool("course") ctx.Data["EnableCloudBrain"] = true + ctx.Data["ActivityTpl"] = getActivityTpl() if ctx.HasError() { ctx.HTML(200, tplSignInPhone) @@ -356,6 +366,7 @@ func SignInPostCommon(ctx *context.Context, form auth.SignInForm) { func SignInCloudBrainPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["PageIsCloudBrainLogin"] = true ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login/cloud_brain" + ctx.Data["ActivityTpl"] = getActivityTpl() SignInPostCommon(ctx, form) } @@ -363,6 +374,7 @@ func SignInCloudBrainPost(ctx *context.Context, form auth.SignInForm) { func SignInPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["PageIsLogin"] = true ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" + ctx.Data["ActivityTpl"] = getActivityTpl() SignInPostCommon(ctx, form) } @@ -1257,6 +1269,7 @@ func SignUp(ctx *context.Context) { //Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration + ctx.Data["ActivityTpl"] = getActivityTpl() ctx.HTML(200, tplSignUp) } @@ -1272,6 +1285,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["PageIsSignUp"] = true + ctx.Data["ActivityTpl"] = getActivityTpl() //Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true if setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration { diff --git a/services/cloudbrain/cloudbrainTask/notebook.go b/services/cloudbrain/cloudbrainTask/notebook.go index 3526f6549..ff1a2100a 100644 --- a/services/cloudbrain/cloudbrainTask/notebook.go +++ b/services/cloudbrain/cloudbrainTask/notebook.go @@ -35,6 +35,7 @@ const NoteBookExtension = ".ipynb" const CPUType = 0 const GPUType = 1 const NPUType = 2 +const CharacterLength = 2550 func FileNotebookCreate(ctx *context.Context, option api.CreateFileNotebookJobOption) { @@ -46,6 +47,14 @@ func FileNotebookCreate(ctx *context.Context, option api.CreateFileNotebookJobOp ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.notebook_select_wrong"))) return } + if len(getBootFile(option.File, option.OwnerName, option.ProjectName)) > CharacterLength { + ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.notebook_path_too_long"))) + return + } + if len(option.BranchName) > CharacterLength { + ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.notebook_branch_name_too_long"))) + return + } isNotebookFileExist, _ := isNoteBookFileExist(ctx, option) if !isNotebookFileExist { @@ -105,14 +114,29 @@ func FileNotebookCreate(ctx *context.Context, option api.CreateFileNotebookJobOp err = downloadCode(sourceRepo, getCodePath(noteBook.JobName, sourceRepo), option.BranchName) if err != nil { log.Error("download code failed", err) - ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("cloudbrain.load_code_failed"))) - return + if !strings.Contains(err.Error(), "already exists and is not an empty directory") { + ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("cloudbrain.load_code_failed"))) + return + } } } if !isRepoFileMatch(option, noteBook) { - noteBook.BootFile += ";" + getBootFile(option.File, option.OwnerName, option.ProjectName) - noteBook.BranchName += ";" + option.BranchName - noteBook.Description += ";" + getDescription(option) + if len(noteBook.BootFile)+len(getBootFile(option.File, option.OwnerName, option.ProjectName))+1 <= CharacterLength { + noteBook.BootFile += ";" + getBootFile(option.File, option.OwnerName, option.ProjectName) + } else { + ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.notebook_path_too_long"))) + return + } + if len(noteBook.BranchName)+len(option.BranchName)+1 <= CharacterLength { + noteBook.BranchName += ";" + option.BranchName + } else { + ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.notebook_branch_name_too_long"))) + return + } + + if len(noteBook.Description)+len(getDescription(option))+1 <= CharacterLength { + noteBook.Description += ";" + getDescription(option) + } err := models.UpdateJob(noteBook) if err != nil { @@ -417,7 +441,7 @@ func cloudBrainFileNoteBookCreate(ctx *context.Context, option api.CreateFileNot } func getCloudbrainType(optionType int) int { - if optionType < 1 { + if optionType <= GPUType { return models.TypeCloudBrainOne } if setting.ModelartsCD.Enabled { @@ -431,7 +455,11 @@ func getCodePath(jobName string, repo *models.Repository) string { } func getDescription(option api.CreateFileNotebookJobOption) string { - return option.OwnerName + "/" + option.ProjectName + "/" + option.File + des := option.OwnerName + "/" + option.ProjectName + "/" + option.File + if len(des) <= CharacterLength { + return des + } + return "" } func modelartsFileNoteBookCreate(ctx *context.Context, option api.CreateFileNotebookJobOption, repo *models.Repository, sourceRepo *models.Repository) { diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index 3a35e69a3..a6c09440e 100755 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -1,3 +1,13 @@ +