| @@ -40,6 +40,7 @@ var ( | |||||
| DisabledRepoUnits []string | DisabledRepoUnits []string | ||||
| DefaultRepoUnits []string | DefaultRepoUnits []string | ||||
| PrefixArchiveFiles bool | PrefixArchiveFiles bool | ||||
| RepoMaxSize int64 | |||||
| // Repository editor settings | // Repository editor settings | ||||
| Editor struct { | Editor struct { | ||||
| @@ -54,6 +55,7 @@ var ( | |||||
| AllowedTypes []string `delim:"|"` | AllowedTypes []string `delim:"|"` | ||||
| FileMaxSize int64 | FileMaxSize int64 | ||||
| MaxFiles int | MaxFiles int | ||||
| TotalMaxSize int64 | |||||
| } `ini:"-"` | } `ini:"-"` | ||||
| // Repository local settings | // Repository local settings | ||||
| @@ -104,6 +106,7 @@ var ( | |||||
| DisabledRepoUnits: []string{}, | DisabledRepoUnits: []string{}, | ||||
| DefaultRepoUnits: []string{}, | DefaultRepoUnits: []string{}, | ||||
| PrefixArchiveFiles: true, | PrefixArchiveFiles: true, | ||||
| RepoMaxSize: 1024, | |||||
| // Repository editor settings | // Repository editor settings | ||||
| Editor: struct { | Editor: struct { | ||||
| @@ -121,12 +124,14 @@ var ( | |||||
| AllowedTypes []string `delim:"|"` | AllowedTypes []string `delim:"|"` | ||||
| FileMaxSize int64 | FileMaxSize int64 | ||||
| MaxFiles int | MaxFiles int | ||||
| TotalMaxSize int64 | |||||
| }{ | }{ | ||||
| Enabled: true, | Enabled: true, | ||||
| TempPath: "data/tmp/uploads", | TempPath: "data/tmp/uploads", | ||||
| AllowedTypes: []string{}, | AllowedTypes: []string{}, | ||||
| FileMaxSize: 3, | |||||
| MaxFiles: 5, | |||||
| FileMaxSize: 30, | |||||
| MaxFiles: 10, | |||||
| TotalMaxSize: 1024, | |||||
| }, | }, | ||||
| // Repository local settings | // Repository local settings | ||||
| @@ -1260,6 +1260,10 @@ editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s | |||||
| editor.no_commit_to_branch = Unable to commit directly to branch because: | editor.no_commit_to_branch = Unable to commit directly to branch because: | ||||
| editor.user_no_push_to_branch = User cannot push to branch | editor.user_no_push_to_branch = User cannot push to branch | ||||
| editor.require_signed_commit = Branch requires a signed commit | editor.require_signed_commit = Branch requires a signed commit | ||||
| editor.repo_too_large = Repository can not exceed %d MB | |||||
| editor.repo_file_invalid = Upload files are invalid | |||||
| editor.upload_file_too_much = Can not upload more than %d files at a time | |||||
| commits.desc = Browse source code change history. | commits.desc = Browse source code change history. | ||||
| commits.commits = Commits | commits.commits = Commits | ||||
| @@ -2867,6 +2871,8 @@ uploading = Uploading | |||||
| upload_complete = Uploading complete | upload_complete = Uploading complete | ||||
| failed = Upload Failed | failed = Upload Failed | ||||
| enable_minio_support = Enable minio support to use the dataset service | enable_minio_support = Enable minio support to use the dataset service | ||||
| max_file_tooltips= Upload a maximum of ? files at a time, each file does not exceed ? MB. | |||||
| max_size_tooltips= You can only upload a maximum of ? files at a time. The upload limit has been reached, please do not add more files. | |||||
| [notification] | [notification] | ||||
| notifications = Notifications | notifications = Notifications | ||||
| @@ -1267,6 +1267,9 @@ editor.cannot_commit_to_protected_branch=不可以提交到受保护的分支 '% | |||||
| editor.no_commit_to_branch=无法直接提交分支,因为: | editor.no_commit_to_branch=无法直接提交分支,因为: | ||||
| editor.user_no_push_to_branch=用户不能推送到分支 | editor.user_no_push_to_branch=用户不能推送到分支 | ||||
| editor.require_signed_commit=分支需要签名提交 | editor.require_signed_commit=分支需要签名提交 | ||||
| editor.repo_too_large = 代码仓总大小不能超过%dMB | |||||
| editor.repo_file_invalid = 提交的文件非法 | |||||
| editor.upload_file_too_much = 不能同时提交超过%d个文件 | |||||
| commits.desc=浏览代码修改历史 | commits.desc=浏览代码修改历史 | ||||
| commits.commits=次代码提交 | commits.commits=次代码提交 | ||||
| @@ -2873,6 +2876,8 @@ uploading=正在上传 | |||||
| upload_complete=上传完成 | upload_complete=上传完成 | ||||
| failed=上传失败 | failed=上传失败 | ||||
| enable_minio_support=启用minio支持以使用数据集服务 | enable_minio_support=启用minio支持以使用数据集服务 | ||||
| max_file_tooltips=单次最多上传?个文件,每个文件不超过? MB。 | |||||
| max_size_tooltips=一次最多只能上传?个文件, 上传已达到上限,请勿再添加文件。 | |||||
| [notification] | [notification] | ||||
| notifications=通知 | notifications=通知 | ||||
| @@ -5,6 +5,7 @@ | |||||
| package repo | package repo | ||||
| import ( | import ( | ||||
| repo_service "code.gitea.io/gitea/services/repository" | |||||
| "encoding/json" | "encoding/json" | ||||
| "fmt" | "fmt" | ||||
| "io/ioutil" | "io/ioutil" | ||||
| @@ -614,6 +615,19 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) { | |||||
| message += "\n\n" + form.CommitMessage | message += "\n\n" + form.CommitMessage | ||||
| } | } | ||||
| if err := repo_service.CheckPushSizeLimit4Web(ctx.Repo.Repository, form.Files); err != nil { | |||||
| if repo_service.IsRepoTooLargeErr(err) { | |||||
| ctx.RenderWithErr(ctx.Tr("repo.editor.repo_too_large", setting.Repository.RepoMaxSize), tplUploadFile, &form) | |||||
| } else if repo_service.IsUploadFileInvalidErr(err) { | |||||
| ctx.RenderWithErr(ctx.Tr("repo.editor.repo_file_invalid"), tplUploadFile, &form) | |||||
| } else if repo_service.IsUploadFileTooMuchErr(err) { | |||||
| ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_too_much", setting.Repository.Upload.MaxFiles), tplUploadFile, &form) | |||||
| } else { | |||||
| ctx.RenderWithErr(err.Error(), tplUploadFile, &form) | |||||
| } | |||||
| return | |||||
| } | |||||
| if err := repofiles.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &repofiles.UploadRepoFileOptions{ | if err := repofiles.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &repofiles.UploadRepoFileOptions{ | ||||
| LastCommitID: ctx.Repo.CommitID, | LastCommitID: ctx.Repo.CommitID, | ||||
| OldBranch: oldBranchName, | OldBranch: oldBranchName, | ||||
| @@ -8,6 +8,7 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "io/ioutil" | "io/ioutil" | ||||
| "net/http" | "net/http" | ||||
| "os" | |||||
| "strings" | "strings" | ||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| @@ -172,3 +173,137 @@ func RecommendFromPromote(url string) ([]string, error) { | |||||
| } | } | ||||
| return result, nil | return result, nil | ||||
| } | } | ||||
| func CheckPushSizeLimit4Web(repo *models.Repository, fileIds []string) error { | |||||
| if err := CheckRepoNumOnceLimit(len(fileIds)); err != nil { | |||||
| return err | |||||
| } | |||||
| totalSize, err := CountUploadFileSizeByIds(fileIds) | |||||
| if err != nil { | |||||
| return UploadFileInvalidErr{} | |||||
| } | |||||
| if err := CheckRepoTotalSizeLimit(repo, totalSize); err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func CheckPushSizeLimit4Http(repo *models.Repository, uploadFileSize int64) error { | |||||
| if err := CheckRepoOnceTotalSizeLimit(uploadFileSize); err != nil { | |||||
| return err | |||||
| } | |||||
| if err := CheckRepoTotalSizeLimit(repo, uploadFileSize); err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func CheckRepoTotalSizeLimit(repo *models.Repository, uploadFileSize int64) error { | |||||
| if repo.Size+uploadFileSize > setting.Repository.RepoMaxSize*1024*1024 { | |||||
| return RepoTooLargeErr{} | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func CheckRepoOnceTotalSizeLimit(uploadFileSize int64) error { | |||||
| if uploadFileSize > setting.Repository.Upload.TotalMaxSize*1024*1024 { | |||||
| return UploadFileTooLargeErr{} | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func CheckRepoNumOnceLimit(uploadFileNum int) error { | |||||
| if uploadFileNum > setting.Repository.Upload.MaxFiles { | |||||
| return UploadFileTooMuchErr{} | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func CountUploadFileSizeByIds(fileIds []string) (int64, error) { | |||||
| if len(fileIds) == 0 { | |||||
| return 0, nil | |||||
| } | |||||
| uploads, err := models.GetUploadsByUUIDs(fileIds) | |||||
| if err != nil { | |||||
| return 0, fmt.Errorf("CountUploadFileSizeByIds error [uuids: %v]: %v", fileIds, err) | |||||
| } | |||||
| var totalSize int64 | |||||
| for _, upload := range uploads { | |||||
| size, err := GetUploadFileSize(upload) | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| totalSize += size | |||||
| } | |||||
| return totalSize, nil | |||||
| } | |||||
| func GetUploadFileSize(upload *models.Upload) (int64, error) { | |||||
| info, err := os.Lstat(upload.LocalPath()) | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| return info.Size(), nil | |||||
| } | |||||
| type RepoTooLargeErr struct { | |||||
| } | |||||
| func (RepoTooLargeErr) Error() string { | |||||
| return fmt.Sprintf("Repository can not exceed %d MB. Please remove some unnecessary files and try again", setting.Repository.RepoMaxSize) | |||||
| } | |||||
| func IsRepoTooLargeErr(err error) bool { | |||||
| _, ok := err.(RepoTooLargeErr) | |||||
| return ok | |||||
| } | |||||
| type UploadFileTooLargeErr struct { | |||||
| } | |||||
| func (UploadFileTooLargeErr) Error() string { | |||||
| return fmt.Sprintf("Upload files can not exceed %d MB at a time", setting.Repository.Upload.TotalMaxSize) | |||||
| } | |||||
| func IsUploadFileTooLargeErr(err error) bool { | |||||
| _, ok := err.(UploadFileTooLargeErr) | |||||
| return ok | |||||
| } | |||||
| type RepoFileTooLargeErr struct { | |||||
| } | |||||
| func (RepoFileTooLargeErr) Error() string { | |||||
| return "repository file is too large" | |||||
| } | |||||
| func IsRepoFileTooLargeErr(err error) bool { | |||||
| _, ok := err.(RepoFileTooLargeErr) | |||||
| return ok | |||||
| } | |||||
| type UploadFileTooMuchErr struct { | |||||
| } | |||||
| func (UploadFileTooMuchErr) Error() string { | |||||
| return "upload files are too lmuch" | |||||
| } | |||||
| func IsUploadFileTooMuchErr(err error) bool { | |||||
| _, ok := err.(UploadFileTooMuchErr) | |||||
| return ok | |||||
| } | |||||
| type UploadFileInvalidErr struct { | |||||
| } | |||||
| func (UploadFileInvalidErr) Error() string { | |||||
| return "upload files are invalid" | |||||
| } | |||||
| func IsUploadFileInvalidErr(err error) bool { | |||||
| _, ok := err.(UploadFileInvalidErr) | |||||
| return ok | |||||
| } | |||||
| @@ -42,18 +42,15 @@ | |||||
| .active{ | .active{ | ||||
| color:#0366D6 !important; | color:#0366D6 !important; | ||||
| } | } | ||||
| .mleft{ | |||||
| margin-left: 30% !important; | |||||
| } | |||||
| .mbom{ | .mbom{ | ||||
| margin-bottom: 10px !important; | margin-bottom: 10px !important; | ||||
| } | } | ||||
| </style> | </style> | ||||
| <div class="row"> | <div class="row"> | ||||
| <div class="ui secondary pointing tabular top attached borderless menu navbar mbom"> | |||||
| <div class="ui secondary tiny pointing borderless menu center aligned grid mbom"> | |||||
| {{with .Org}} | {{with .Org}} | ||||
| <a class="{{if $.PageIsOrgHome}}active{{end}} item mleft" href="{{.HomeLink}}"> | |||||
| <a class="{{if $.PageIsOrgHome}}active{{end}} item" href="{{.HomeLink}}"> | |||||
| {{svg "octicon-home" 16}} {{$.i18n.Tr "org.home"}} | {{svg "octicon-home" 16}} {{$.i18n.Tr "org.home"}} | ||||
| </a> | </a> | ||||
| {{end}} | {{end}} | ||||
| @@ -83,47 +83,36 @@ | |||||
| {{ range .tags}} | {{ range .tags}} | ||||
| {{if eq .TagName "精选项目"}} | {{if eq .TagName "精选项目"}} | ||||
| <div class="ui three cards" style="margin-bottom: 10px;"> | |||||
| <div class="ui three stackable cards" style="margin-bottom: 10px;"> | |||||
| {{ range .RepoList}} | {{ range .RepoList}} | ||||
| <div class="card" > | |||||
| <div class="ui raised card"> | |||||
| <div class="extra full_height cor" > | |||||
| <div class=" header header_card omit" > | |||||
| <a class="header_card image poping up " href="{{.Link}}" data-content="{{if .Alias}}{{.Alias}}{{else}}{{.Name}}{{end}}" data-position="top left" data-variation="tiny inverted">{{if .Alias}}{{.Alias}}{{else}}{{.Name}}{{end}}</a> | |||||
| <div class="content" style="padding-bottom: 0;"> | |||||
| <div class="header" > | |||||
| <a href="{{.Link}}">{{if .Alias}}{{.Alias}}{{else}}{{.Name}}{{end}}</a> | |||||
| </div> | </div> | ||||
| <div class='content descript_height nowrap-2'> | |||||
| <div class="description"> | |||||
| <p class="nowrap-2"> | |||||
| {{.Description}} | {{.Description}} | ||||
| </div> | |||||
| <div class="content " > | |||||
| </p> | |||||
| {{if .Topics }} | {{if .Topics }} | ||||
| <div class=" tags " style="position: relative;"> | |||||
| {{range .Topics}} | |||||
| {{if ne . "" }}<a style="max-width:100%;display:inline-flex;" href="{{AppSubUrl}}/explore/repos?q={{.}}&topic={{$.Topic}}" ><span class="ui small label topic omit" >{{.}}</span></a>{{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| {{end}} | |||||
| <p> | |||||
| {{range .Topics}} | |||||
| {{if ne . "" }}<a href="{{AppSubUrl}}/explore/repos?q={{.}}&topic={{$.Topic}}" class="ui small label topic omit" >{{.}}</a>{{end}} | |||||
| {{end}} | |||||
| </p> | |||||
| {{end}} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div class=" extra " style="color:#888888;border-top: none !important;padding-top: 0"> | |||||
| <div class="ui mini right compact marg" > | |||||
| <a class="item marg "> | |||||
| {{svg "octicon-eye" 16}} {{.NumWatches}} | |||||
| </a> | |||||
| <a class="item marg"> | |||||
| {{svg "octicon-star" 16}} {{.NumStars}} | |||||
| </a> | |||||
| <a class="item marg"> | |||||
| {{svg "octicon-git-branch" 16}} {{.NumForks}} | |||||
| </a> | |||||
| </div> | |||||
| <div class="extra content"> | |||||
| <span class="right floated date"> | |||||
| {{svg "octicon-eye" 16}} {{.NumWatches}}     | |||||
| {{svg "octicon-star" 16}} {{.NumStars}}     | |||||
| {{svg "octicon-git-branch" 16}} {{.NumForks}} | |||||
| </span> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| @@ -27,10 +27,10 @@ | |||||
| </div> | </div> | ||||
| <div class="field"> | <div class="field"> | ||||
| <div class="files"></div> | <div class="files"></div> | ||||
| <div class="ui dropzone" id="dropzone" data-upload-url="{{.RepoLink}}/upload-file" data-remove-url="{{.RepoLink}}/upload-remove" data-csrf="{{.CsrfToken}}" data-accepts="{{.UploadAllowedTypes}}" data-max-file="{{.UploadMaxFiles}}" data-max-size="{{.UploadMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div> | |||||
| <div class="ui dropzone" id="dropzone" data-upload-url="{{.RepoLink}}/upload-file" data-remove-url="{{.RepoLink}}/upload-remove" data-csrf="{{.CsrfToken}}" data-accepts="{{.UploadAllowedTypes}}" data-max-file="{{.UploadMaxFiles}}" data-max-size="{{.UploadMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}" data-max-file-tooltips="{{.i18n.Tr "dropzone.max_file_tooltips"}}" data-max-size-tooltips="{{.i18n.Tr "dropzone.max_size_tooltips"}}"><div class="maxfilesize ui red message" style="display: none;margin: 2.5rem;"></div></div> | |||||
| </div> | </div> | ||||
| {{template "repo/editor/commit_form" .}} | {{template "repo/editor/commit_form" .}} | ||||
| </form> | </form> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{template "base/footer" .}} | |||||
| {{template "base/footer" .}} | |||||
| @@ -2751,12 +2751,25 @@ $(document).ready(async () => { | |||||
| $('td[data-href]').click(function () { | $('td[data-href]').click(function () { | ||||
| window.location = $(this).data('href'); | window.location = $(this).data('href'); | ||||
| }); | }); | ||||
| // 在String原型对象上添加format方法 | |||||
| String.prototype.format = function(){ | |||||
| let str = this; | |||||
| if(arguments.length == 0){ | |||||
| return str; | |||||
| }else{ | |||||
| Object.keys(arguments).forEach((item,index)=>{ | |||||
| str = str.replace(/\?/,arguments[item]) | |||||
| }) | |||||
| return str | |||||
| } | |||||
| } | |||||
| // Dropzone | // Dropzone | ||||
| const $dropzone = $('#dropzone'); | const $dropzone = $('#dropzone'); | ||||
| if ($dropzone.length > 0) { | if ($dropzone.length > 0) { | ||||
| const filenameDict = {}; | const filenameDict = {}; | ||||
| let maxFileTooltips=$dropzone.data('max-file-tooltips').format($dropzone.data('max-file'),$dropzone.data('max-size')) | |||||
| let maxSizeTooltips=$dropzone.data('max-size-tooltips').format($dropzone.data('max-file')) | |||||
| await createDropzone('#dropzone', { | await createDropzone('#dropzone', { | ||||
| url: $dropzone.data('upload-url'), | url: $dropzone.data('upload-url'), | ||||
| headers: {'X-Csrf-Token': csrf}, | headers: {'X-Csrf-Token': csrf}, | ||||
| @@ -2788,6 +2801,22 @@ $(document).ready(async () => { | |||||
| }); | }); | ||||
| } | } | ||||
| }); | }); | ||||
| this.on('addedfile',(file)=>{ | |||||
| if(file.size/(1000*1000)>$dropzone.data('max-size')){ | |||||
| this.removeFile(file) | |||||
| $('.maxfilesize.ui.red.message').text(maxFileTooltips) | |||||
| $('.maxfilesize.ui.red.message').css('display','block') | |||||
| }else{ | |||||
| $('.maxfilesize.ui.red.message').css('display','none') | |||||
| } | |||||
| }); | |||||
| this.on('maxfilesexceeded',(file)=>{ | |||||
| this.removeFile(file) | |||||
| $('.maxfilesize.ui.red.message').text(maxSizeTooltips) | |||||
| $('.maxfilesize.ui.red.message').css('display','block') | |||||
| }) | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -12,6 +12,11 @@ a { | |||||
| .ui .text.yellow a:hover { | .ui .text.yellow a:hover { | ||||
| color: #f2711c!important | color: #f2711c!important | ||||
| } | } | ||||
| .ui.small.label.topic{ | |||||
| margin-bottom: 0; | |||||
| font-weight: 400; | |||||
| } | |||||
| .mb-1 { | .mb-1 { | ||||
| margin-bottom: 8px !important; | margin-bottom: 8px !important; | ||||
| } | } | ||||
| @@ -142,6 +147,11 @@ footer { | |||||
| width:auto; | width:auto; | ||||
| margin:10px auto; | margin:10px auto; | ||||
| } | } | ||||
| .ui.card>.extra, .ui.cards>.card>.extra{ | |||||
| border-top:none !important; | |||||
| padding: .5em .5em 1em; | |||||
| } | |||||
| #index-project .ui.card>.content, #index-project.ui.cards>.card>.content{ | #index-project .ui.card>.content, #index-project.ui.cards>.card>.content{ | ||||
| padding: 0.5em 0.2em; | padding: 0.5em 0.2em; | ||||
| } | } | ||||