From 280c8272f8a4cea0cea8f5eafceb9ed779ba0283 Mon Sep 17 00:00:00 2001 From: Xavier Duthil Date: Fri, 5 Mar 2021 15:00:22 +0100 Subject: [PATCH 001/135] Use exec in all components' entrypoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the exec Bash command so that the final running application becomes the container’s PID 1. This allows the application to receive any Unix signals sent to the container, in accordance with https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#entrypoint Currently, SIGTERM signals sent by kubernetes are not passed to the executed binary. Signed-off-by: Xavier Duthil --- make/photon/chartserver/docker-entrypoint.sh | 4 +--- make/photon/core/entrypoint.sh | 2 +- make/photon/exporter/entrypoint.sh | 2 +- make/photon/jobservice/entrypoint.sh | 2 +- make/photon/registry/entrypoint.sh | 2 +- make/photon/trivy-adapter/entrypoint.sh | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/make/photon/chartserver/docker-entrypoint.sh b/make/photon/chartserver/docker-entrypoint.sh index 1a5f87d4c9b..0849a61b72b 100644 --- a/make/photon/chartserver/docker-entrypoint.sh +++ b/make/photon/chartserver/docker-entrypoint.sh @@ -5,6 +5,4 @@ set -e /home/chart/install_cert.sh #Start the server process -/home/chart/chartm - -set +e +exec /home/chart/chartm diff --git a/make/photon/core/entrypoint.sh b/make/photon/core/entrypoint.sh index 40aa646a865..2e2e4bf7921 100644 --- a/make/photon/core/entrypoint.sh +++ b/make/photon/core/entrypoint.sh @@ -4,4 +4,4 @@ set -e /harbor/install_cert.sh -/harbor/harbor_core +exec /harbor/harbor_core diff --git a/make/photon/exporter/entrypoint.sh b/make/photon/exporter/entrypoint.sh index d5bd5c9edd9..cffa89bdc71 100644 --- a/make/photon/exporter/entrypoint.sh +++ b/make/photon/exporter/entrypoint.sh @@ -4,4 +4,4 @@ set -e /harbor/install_cert.sh -/harbor/harbor_exporter +exec /harbor/harbor_exporter diff --git a/make/photon/jobservice/entrypoint.sh b/make/photon/jobservice/entrypoint.sh index 9c442c8c6b0..ad7501b08db 100644 --- a/make/photon/jobservice/entrypoint.sh +++ b/make/photon/jobservice/entrypoint.sh @@ -4,4 +4,4 @@ set -e /harbor/install_cert.sh -/harbor/harbor_jobservice -c /etc/jobservice/config.yml +exec /harbor/harbor_jobservice -c /etc/jobservice/config.yml diff --git a/make/photon/registry/entrypoint.sh b/make/photon/registry/entrypoint.sh index 12840637033..c762e56c495 100644 --- a/make/photon/registry/entrypoint.sh +++ b/make/photon/registry/entrypoint.sh @@ -10,4 +10,4 @@ set -e /home/harbor/install_cert.sh -/usr/bin/registry_DO_NOT_USE_GC serve /etc/registry/config.yml +exec /usr/bin/registry_DO_NOT_USE_GC serve /etc/registry/config.yml diff --git a/make/photon/trivy-adapter/entrypoint.sh b/make/photon/trivy-adapter/entrypoint.sh index e938f092392..d84d913fea5 100644 --- a/make/photon/trivy-adapter/entrypoint.sh +++ b/make/photon/trivy-adapter/entrypoint.sh @@ -4,4 +4,4 @@ set -e /home/scanner/install_cert.sh -/home/scanner/bin/scanner-trivy \ No newline at end of file +exec /home/scanner/bin/scanner-trivy From 530855e9ad3dae1cab2e5c1ad960977eba66d676 Mon Sep 17 00:00:00 2001 From: Daniel Pacak Date: Tue, 29 Jun 2021 08:24:47 +0200 Subject: [PATCH 002/135] chore(trivy): Bump up Trivy adapter from v0.19.0 to v0.20.0 This version of the adapter service wraps Trivy v0.18.3 that supports Go dependency scanning and various other improvements. Signed-off-by: Daniel Pacak --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8b260e49537..e99d3ee65f7 100644 --- a/Makefile +++ b/Makefile @@ -110,8 +110,8 @@ PREPARE_VERSION_NAME=versions REGISTRYVERSION=v2.7.1-patch-2819-2553-redis NOTARYVERSION=v0.6.1 NOTARYMIGRATEVERSION=v4.11.0 -TRIVYVERSION=v0.17.2 -TRIVYADAPTERVERSION=v0.19.0 +TRIVYVERSION=v0.18.3 +TRIVYADAPTERVERSION=v0.20.0 # version of chartmuseum for pulling the source code CHARTMUSEUM_SRC_TAG=v0.13.1 From b73480ed0c791e67c48ee41fa1f8ef353b210f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E4=B8=96=E5=86=9B?= <30999793+AllForNothing@users.noreply.github.com> Date: Fri, 20 Aug 2021 12:08:39 +0800 Subject: [PATCH 003/135] Improve css style for autofill input (#15457) Signed-off-by: AllForNothing --- src/portal/src/css/common.scss | 6 ++++++ src/portal/src/css/dark-theme.scss | 2 ++ src/portal/src/css/light-theme.scss | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/portal/src/css/common.scss b/src/portal/src/css/common.scss index aee5b3aa324..8ab269ca32f 100644 --- a/src/portal/src/css/common.scss +++ b/src/portal/src/css/common.scss @@ -275,3 +275,9 @@ app-tag-feature-integration { background-color: $radio-button-checked; } } + +input:-webkit-autofill { + -webkit-box-shadow: 0 0 0 30px $input-autofill-bg-color inset; + -webkit-text-fill-color: $input-autofill-color !important; + caret-color: $input-autofill-color +} diff --git a/src/portal/src/css/dark-theme.scss b/src/portal/src/css/dark-theme.scss index a32b01b1ec0..edbcc0156af 100644 --- a/src/portal/src/css/dark-theme.scss +++ b/src/portal/src/css/dark-theme.scss @@ -43,4 +43,6 @@ $label-color-input: #ddd; $central-block-loading-bg-color: rgba(0, 0, 0, 0.5); $back-link-color: #4aaed9!important; $radio-button-checked: #4aaed9; +$input-autofill-bg-color: #1b2a32; +$input-autofill-color: #eaedf0; @import "./common.scss"; diff --git a/src/portal/src/css/light-theme.scss b/src/portal/src/css/light-theme.scss index 9666803b6e8..992a20e8e1c 100644 --- a/src/portal/src/css/light-theme.scss +++ b/src/portal/src/css/light-theme.scss @@ -45,4 +45,6 @@ $label-color-input: #5d5d5d; $central-block-loading-bg-color: rgba(255, 255, 255, 0.5); $back-link-color: none; $radio-button-checked: #0072a3; +$input-autofill-bg-color: #fafafa; +$input-autofill-color: #000; @import "./common.scss"; From b9228096dc4b5058cb7e2e80d7440e3421bf686c Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Tue, 24 Aug 2021 09:34:02 +0800 Subject: [PATCH 004/135] enable robot to support create project (#15461) 1, for admin only, the system level robot should contains the project creation access. 2, for not admin only, the system level robot can create project. 3, for the project that created by system level robot, use the admin ID as the ownerID. No path for project level robot to create project. Signed-off-by: wang yan --- src/controller/robot/model.go | 10 ++++++ src/controller/robot/model_test.go | 44 +++++++++++++++++++++-- src/controller/user/controller.go | 11 +++--- src/pkg/user/manager.go | 16 ++++----- src/pkg/user/models/user.go | 22 ++++++++++++ src/server/v2.0/handler/project.go | 38 +++++++++++++++----- src/testing/controller/user/controller.go | 27 +++++++++----- src/testing/pkg/user/manager.go | 21 +++++++---- 8 files changed, 148 insertions(+), 41 deletions(-) diff --git a/src/controller/robot/model.go b/src/controller/robot/model.go index aaa364c58f2..5f9ada44621 100644 --- a/src/controller/robot/model.go +++ b/src/controller/robot/model.go @@ -31,6 +31,11 @@ type Robot struct { Permissions []*Permission `json:"permissions"` } +// IsSysLevel, true is a system level robot, others are project level. +func (r *Robot) IsSysLevel() bool { + return r.Level == LEVELSYSTEM +} + // setLevel, 0 is a system level robot, others are project level. func (r *Robot) setLevel() { if r.ProjectID == 0 { @@ -56,6 +61,11 @@ type Permission struct { Scope string `json:"-"` } +// IsCoverAll ... +func (p *Permission) IsCoverAll() bool { + return p.Scope == SCOPEALLPROJECT +} + // Option ... type Option struct { WithPermission bool diff --git a/src/controller/robot/model_test.go b/src/controller/robot/model_test.go index 6ebdeab6be7..f307655b957 100644 --- a/src/controller/robot/model_test.go +++ b/src/controller/robot/model_test.go @@ -31,6 +31,24 @@ func (suite *ModelTestSuite) TestSetLevel() { suite.Equal(LEVELPROJECT, r.Level) } +func (suite *ModelTestSuite) TestIsSysLevel() { + r := Robot{ + Robot: model.Robot{ + ProjectID: 0, + }, + } + r.setLevel() + suite.True(r.IsSysLevel()) + + r = Robot{ + Robot: model.Robot{ + ProjectID: 1, + }, + } + r.setLevel() + suite.False(r.IsSysLevel()) +} + func (suite *ModelTestSuite) TestSetEditable() { r := Robot{ Robot: model.Robot{ @@ -38,7 +56,7 @@ func (suite *ModelTestSuite) TestSetEditable() { }, } r.setEditable() - suite.Equal(false, r.Editable) + suite.False(r.Editable) r = Robot{ Robot: model.Robot{ @@ -66,7 +84,29 @@ func (suite *ModelTestSuite) TestSetEditable() { }, } r.setEditable() - suite.Equal(true, r.Editable) + suite.True(r.Editable) +} + +func (suite *ModelTestSuite) TestIsCoverAll() { + p := &Permission{ + Kind: "project", + Namespace: "library", + Access: []*types.Policy{ + { + Resource: "repository", + Action: "push", + }, + { + Resource: "repository", + Action: "pull", + }, + }, + Scope: "/project/*", + } + suite.True(p.IsCoverAll()) + + p.Scope = "/system" + suite.False(p.IsCoverAll()) } func TestModelTestSuite(t *testing.T) { diff --git a/src/controller/user/controller.go b/src/controller/user/controller.go index fa0e46874e5..1786cd1e473 100644 --- a/src/controller/user/controller.go +++ b/src/controller/user/controller.go @@ -18,14 +18,13 @@ import ( "context" "fmt" "github.com/goharbor/harbor/src/common" - "github.com/goharbor/harbor/src/lib" - "github.com/goharbor/harbor/src/pkg/member" - commonmodels "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security/local" + "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/member" "github.com/goharbor/harbor/src/pkg/oidc" "github.com/goharbor/harbor/src/pkg/user" "github.com/goharbor/harbor/src/pkg/user/models" @@ -45,7 +44,7 @@ type Controller interface { // UpdatePassword ... UpdatePassword(ctx context.Context, id int, password string) error // List ... - List(ctx context.Context, query *q.Query) ([]*models.User, error) + List(ctx context.Context, query *q.Query, options ...models.Option) (models.Users, error) // Create ... Create(ctx context.Context, u *models.User) (int, error) // Count ... @@ -185,8 +184,8 @@ func (c *controller) Delete(ctx context.Context, id int) error { return c.mgr.Delete(ctx, id) } -func (c *controller) List(ctx context.Context, query *q.Query) ([]*models.User, error) { - return c.mgr.List(ctx, query) +func (c *controller) List(ctx context.Context, query *q.Query, options ...models.Option) (models.Users, error) { + return c.mgr.List(ctx, query, options...) } func (c *controller) UpdatePassword(ctx context.Context, id int, password string) error { diff --git a/src/pkg/user/manager.go b/src/pkg/user/manager.go index 385615303a2..c0eeea6631c 100644 --- a/src/pkg/user/manager.go +++ b/src/pkg/user/manager.go @@ -17,14 +17,13 @@ package user import ( "context" "fmt" - "strings" - "github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/user/dao" "github.com/goharbor/harbor/src/pkg/user/models" + "strings" ) var ( @@ -39,7 +38,7 @@ type Manager interface { // GetByName get user by username, it will return an error if the user does not exist GetByName(ctx context.Context, username string) (*models.User, error) // List users according to the query - List(ctx context.Context, query *q.Query) (models.Users, error) + List(ctx context.Context, query *q.Query, options ...models.Option) (models.Users, error) // Count counts the number of users according to the query Count(ctx context.Context, query *q.Query) (int64, error) // Create creates the user, the password of input should be plaintext @@ -177,21 +176,20 @@ func (m *manager) GetByName(ctx context.Context, username string) (*models.User, } // List users according to the query -func (m *manager) List(ctx context.Context, query *q.Query) (models.Users, error) { +func (m *manager) List(ctx context.Context, query *q.Query, options ...models.Option) (models.Users, error) { query = q.MustClone(query) - excludeAdmin := true for key := range query.Keywords { str := strings.ToLower(key) if str == "user_id__in" { - excludeAdmin = false + options = append(options, models.WithDefaultAdmin()) break } else if str == "user_id" { - excludeAdmin = false + options = append(options, models.WithDefaultAdmin()) break } } - if excludeAdmin { - // Exclude admin account when not filter by UserIDs, see https://github.com/goharbor/harbor/issues/2527 + opts := models.NewOptions(options...) + if !opts.IncludeDefaultAdmin { query.Keywords["user_id__gt"] = 1 } return m.dao.List(ctx, query) diff --git a/src/pkg/user/models/user.go b/src/pkg/user/models/user.go index d8bc7739a86..7463986cdc0 100644 --- a/src/pkg/user/models/user.go +++ b/src/pkg/user/models/user.go @@ -35,3 +35,25 @@ func (users Users) MapByUserID() map[int]*User { return m } + +type Option func(*Options) + +type Options struct { + IncludeDefaultAdmin bool +} + +// WithDefaultAdmin set the IncludeAdmin = true +func WithDefaultAdmin() Option { + return func(o *Options) { + o.IncludeDefaultAdmin = true + } +} + +// NewOptions ... +func NewOptions(options ...Option) *Options { + opts := &Options{} + for _, f := range options { + f(opts) + } + return opts +} diff --git a/src/server/v2.0/handler/project.go b/src/server/v2.0/handler/project.go index 2d60817f33c..43b29afb7c8 100644 --- a/src/server/v2.0/handler/project.go +++ b/src/server/v2.0/handler/project.go @@ -34,8 +34,8 @@ import ( "github.com/goharbor/harbor/src/controller/registry" "github.com/goharbor/harbor/src/controller/repository" "github.com/goharbor/harbor/src/controller/retention" - robotCtr "github.com/goharbor/harbor/src/controller/robot" "github.com/goharbor/harbor/src/controller/scanner" + "github.com/goharbor/harbor/src/controller/user" "github.com/goharbor/harbor/src/core/api" "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/config" @@ -50,7 +50,7 @@ import ( "github.com/goharbor/harbor/src/pkg/quota/types" "github.com/goharbor/harbor/src/pkg/retention/policy" "github.com/goharbor/harbor/src/pkg/robot" - "github.com/goharbor/harbor/src/pkg/user" + userModels "github.com/goharbor/harbor/src/pkg/user/models" "github.com/goharbor/harbor/src/server/v2.0/handler/model" "github.com/goharbor/harbor/src/server/v2.0/models" operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/project" @@ -63,7 +63,7 @@ func newProjectAPI() *projectAPI { return &projectAPI{ auditMgr: audit.Mgr, metadataMgr: metadata.Mgr, - userMgr: user.Mgr, + userCtl: user.Ctl, repositoryCtl: repository.Ctl, projectCtl: project.Ctl, memberMgr: member.Mgr, @@ -79,7 +79,7 @@ type projectAPI struct { BaseAPI auditMgr audit.Manager metadataMgr metadata.Manager - userMgr user.Manager + userCtl user.Controller repositoryCtl repository.Controller projectCtl project.Controller memberMgr member.Manager @@ -101,6 +101,10 @@ func (a *projectAPI) CreateProject(ctx context.Context, params operation.CreateP } secCtx, _ := security.FromContext(ctx) + if r, ok := secCtx.(*robotSec.SecurityContext); ok && !r.User().IsSysLevel() { + log.Errorf("Only system level robot can create project") + return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system level robot can create project")) + } if onlyAdmin && !(a.isSysAdmin(ctx, rbac.ActionCreate) || secCtx.IsSolutionUser()) { log.Errorf("Only sys admin can create project") return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system admin can create project")) @@ -155,14 +159,32 @@ func (a *projectAPI) CreateProject(ctx context.Context, params operation.CreateP } var ownerID int + // TODO: revise the ownerID in project model. // set the owner as the system admin when the API being called by replication // it's a solution to workaround the restriction of project creation API: // only normal users can create projects - if secCtx.IsSolutionUser() { - ownerID = 1 + // Remove the assumption of user id 1 is the system admin. And use the minimum system admin id as the owner ID, + // in most case, it's 1 + if _, ok := secCtx.(*robotSec.SecurityContext); ok || secCtx.IsSolutionUser() { + q := &q.Query{ + Keywords: map[string]interface{}{ + "sysadmin_flag": true, + }, + Sorts: []*q.Sort{ + q.NewSort("user_id", false), + }, + } + admins, err := a.userCtl.List(ctx, q, userModels.WithDefaultAdmin()) + if err != nil { + return a.SendError(ctx, err) + } + if len(admins) == 0 { + return a.SendError(ctx, errors.New(nil).WithMessage("cannot create project as no system admin found")) + } + ownerID = admins[0].UserID } else { ownerName := secCtx.GetUsername() - user, err := a.userMgr.GetByName(ctx, ownerName) + user, err := a.userCtl.GetByName(ctx, ownerName) if err != nil { return a.SendError(ctx, err) } @@ -422,7 +444,7 @@ func (a *projectAPI) ListProjects(ctx context.Context, params operation.ListProj var coverAll bool var names []string for _, p := range r.User().Permissions { - if p.Scope == robotCtr.SCOPEALLPROJECT { + if p.IsCoverAll() { coverAll = true break } diff --git a/src/testing/controller/user/controller.go b/src/testing/controller/user/controller.go index 2be1f93548f..8c9bc29b466 100644 --- a/src/testing/controller/user/controller.go +++ b/src/testing/controller/user/controller.go @@ -11,6 +11,8 @@ import ( q "github.com/goharbor/harbor/src/lib/q" user "github.com/goharbor/harbor/src/controller/user" + + usermodels "github.com/goharbor/harbor/src/pkg/user/models" ) // Controller is an autogenerated mock type for the Controller type @@ -143,22 +145,29 @@ func (_m *Controller) GetBySubIss(ctx context.Context, sub string, iss string) ( return r0, r1 } -// List provides a mock function with given fields: ctx, query -func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*models.User, error) { - ret := _m.Called(ctx, query) +// List provides a mock function with given fields: ctx, query, options +func (_m *Controller) List(ctx context.Context, query *q.Query, options ...usermodels.Option) (usermodels.Users, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) - var r0 []*models.User - if rf, ok := ret.Get(0).(func(context.Context, *q.Query) []*models.User); ok { - r0 = rf(ctx, query) + var r0 usermodels.Users + if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...usermodels.Option) usermodels.Users); ok { + r0 = rf(ctx, query, options...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*models.User) + r0 = ret.Get(0).(usermodels.Users) } } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok { - r1 = rf(ctx, query) + if rf, ok := ret.Get(1).(func(context.Context, *q.Query, ...usermodels.Option) error); ok { + r1 = rf(ctx, query, options...) } else { r1 = ret.Error(1) } diff --git a/src/testing/pkg/user/manager.go b/src/testing/pkg/user/manager.go index 5157da8f714..30dfd86b0a5 100644 --- a/src/testing/pkg/user/manager.go +++ b/src/testing/pkg/user/manager.go @@ -120,13 +120,20 @@ func (_m *Manager) GetByName(ctx context.Context, username string) (*models.User return r0, r1 } -// List provides a mock function with given fields: ctx, query -func (_m *Manager) List(ctx context.Context, query *q.Query) (usermodels.Users, error) { - ret := _m.Called(ctx, query) +// List provides a mock function with given fields: ctx, query, options +func (_m *Manager) List(ctx context.Context, query *q.Query, options ...usermodels.Option) (usermodels.Users, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) var r0 usermodels.Users - if rf, ok := ret.Get(0).(func(context.Context, *q.Query) usermodels.Users); ok { - r0 = rf(ctx, query) + if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...usermodels.Option) usermodels.Users); ok { + r0 = rf(ctx, query, options...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(usermodels.Users) @@ -134,8 +141,8 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) (usermodels.Users, } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok { - r1 = rf(ctx, query) + if rf, ok := ret.Get(1).(func(context.Context, *q.Query, ...usermodels.Option) error); ok { + r1 = rf(ctx, query, options...) } else { r1 = ret.Error(1) } From a8562b29344d6b6dab5e9b1c587b8899baee198d Mon Sep 17 00:00:00 2001 From: armandxu <936215300@qq.com> Date: Tue, 24 Aug 2021 15:09:41 +0800 Subject: [PATCH 005/135] wrong word (#15187) Signed-off-by: armandxu <936215300@qq.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 255d9bab09e..e5c99007666 100644 --- a/Makefile +++ b/Makefile @@ -446,7 +446,7 @@ build_base_docker: if [ -n "$(REGISTRYUSER)" ] && [ -n "$(REGISTRYPASSWORD)" ] ; then \ docker login -u $(REGISTRYUSER) -p $(REGISTRYPASSWORD) ; \ else \ - echo "No docker credentials provided, please make sure enough priviledges to access docker hub!" ; \ + echo "No docker credentials provided, please make sure enough privileges to access docker hub!" ; \ fi @for name in $(BUILDBASETARGET); do \ echo $$name ; \ From eca3d82d9cfb193b88592496836aec56cb0190d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E4=B8=96=E5=86=9B?= <30999793+AllForNothing@users.noreply.github.com> Date: Tue, 24 Aug 2021 17:04:37 +0800 Subject: [PATCH 006/135] Improve global search component (#15462) Signed-off-by: AllForNothing --- .../global-search.component.html | 4 +- .../global-search/global-search.component.ts | 51 ++++++++---- .../list-chart-version-ro.component.html | 2 +- .../list-chart-version-ro.component.ts | 7 +- .../list-project-ro.component.html | 7 +- .../list-project-ro.component.spec.ts | 77 +++++++++++-------- .../list-project-ro.component.ts | 36 +++------ .../list-repository-ro.component.html | 7 +- .../list-repository-ro.component.ts | 63 ++++----------- src/portal/src/i18n/lang/de-de-lang.json | 3 +- src/portal/src/i18n/lang/en-us-lang.json | 3 +- src/portal/src/i18n/lang/es-es-lang.json | 3 +- src/portal/src/i18n/lang/fr-fr-lang.json | 3 +- src/portal/src/i18n/lang/pt-br-lang.json | 3 +- src/portal/src/i18n/lang/tr-tr-lang.json | 3 +- src/portal/src/i18n/lang/zh-cn-lang.json | 3 +- src/portal/src/i18n/lang/zh-tw-lang.json | 3 +- 17 files changed, 133 insertions(+), 145 deletions(-) diff --git a/src/portal/src/app/shared/components/global-search/global-search.component.html b/src/portal/src/app/shared/components/global-search/global-search.component.html index d6848dd4798..ee1ff592501 100644 --- a/src/portal/src/app/shared/components/global-search/global-search.component.html +++ b/src/portal/src/app/shared/components/global-search/global-search.component.html @@ -1,5 +1,5 @@ \ No newline at end of file + diff --git a/src/portal/src/app/shared/components/global-search/global-search.component.ts b/src/portal/src/app/shared/components/global-search/global-search.component.ts index 9abcb45bcc6..c889a1bf943 100644 --- a/src/portal/src/app/shared/components/global-search/global-search.component.ts +++ b/src/portal/src/app/shared/components/global-search/global-search.component.ts @@ -1,4 +1,3 @@ - import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; // Copyright (c) 2017 VMware, Inc. All Rights Reserved. // @@ -13,20 +12,17 @@ import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { Router } from '@angular/router'; -import { Subject , Subscription } from "rxjs"; - +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subject, Subscription } from "rxjs"; import { SearchTriggerService } from './search-trigger.service'; - import { AppConfigService } from '../../../services/app-config.service'; - - - -import {TranslateService} from "@ngx-translate/core"; -import {SkinableConfig} from "../../../services/skinable-config.service"; +import { TranslateService } from "@ngx-translate/core"; +import { SkinableConfig } from "../../../services/skinable-config.service"; +import { Location } from '@angular/common'; const deBounceTime = 500; // ms +const SEARCH_KEY: string = 'globalSearch'; @Component({ selector: 'global-search', @@ -43,13 +39,13 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { // To indicate if the result panel is opened isResPanelOpened: boolean = false; - searchTerm: string = ""; - placeholderText: string; - + private _searchTerm = ""; constructor( private searchTrigger: SearchTriggerService, private router: Router, + private activatedRoute: ActivatedRoute, + private location: Location, private appConfigService: AppConfigService, private translate: TranslateService, private skinableConfig: SkinableConfig) { @@ -71,8 +67,7 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { } this.searchSub = this.searchTerms.pipe( - debounceTime(deBounceTime), - distinctUntilChanged()) + debounceTime(deBounceTime)) .subscribe(term => { this.searchTrigger.triggerSearch(term); }); @@ -83,6 +78,11 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { if (this.appConfigService.isIntegrationMode()) { this.placeholderText = "GLOBAL_SEARCH.PLACEHOLDER_VIC"; } + // init _searchTerm from queryParams + this._searchTerm = this.activatedRoute.snapshot.queryParams[SEARCH_KEY]; + if (this._searchTerm) { + this.searchTerms.next(this._searchTerm); + } } ngOnDestroy(): void { @@ -98,7 +98,24 @@ export class GlobalSearchComponent implements OnInit, OnDestroy { // Handle the term inputting event search(term: string): void { // Send event even term is empty - this.searchTerms.next(term.trim()); } + get searchTerm(): string { + return this._searchTerm; + } + set searchTerm(s) { + let url: string; + if (s) { + url = this.router.createUrlTree([], { + relativeTo: this.activatedRoute, + queryParams: {[SEARCH_KEY]: s} + }).toString(); + } else { + url = this.router.createUrlTree([], { + relativeTo: this.activatedRoute, + }).toString(); + } + this.location.replaceState(url); + this._searchTerm = s; + } } diff --git a/src/portal/src/app/shared/components/list-chart-version-ro/list-chart-version-ro.component.html b/src/portal/src/app/shared/components/list-chart-version-ro/list-chart-version-ro.component.html index 114be4d71bc..d708fb4f5db 100644 --- a/src/portal/src/app/shared/components/list-chart-version-ro/list-chart-version-ro.component.html +++ b/src/portal/src/app/shared/components/list-chart-version-ro/list-chart-version-ro.component.html @@ -22,4 +22,4 @@ {{charts?.length}} {{'HELM_CHART.ITEMS'| translate}} - \ No newline at end of file + diff --git a/src/portal/src/app/shared/components/list-chart-version-ro/list-chart-version-ro.component.ts b/src/portal/src/app/shared/components/list-chart-version-ro/list-chart-version-ro.component.ts index 2c025680990..0c6ff67143b 100644 --- a/src/portal/src/app/shared/components/list-chart-version-ro/list-chart-version-ro.component.ts +++ b/src/portal/src/app/shared/components/list-chart-version-ro/list-chart-version-ro.component.ts @@ -1,5 +1,5 @@ import { Router } from '@angular/router'; -import { Component, OnInit, Input } from '@angular/core'; +import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core'; import { HelmChartSearchResultItem, HelmChartVersion, HelmChartMaintainer } from '../../../base/project/helm-chart/helm-chart-detail/helm-chart.interface.service'; import { SearchTriggerService } from '../global-search/search-trigger.service'; import { ProjectService } from "../../services"; @@ -7,11 +7,10 @@ import { ProjectService } from "../../services"; @Component({ selector: 'list-chart-version-ro', - templateUrl: './list-chart-version-ro.component.html' + templateUrl: './list-chart-version-ro.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) export class ListChartVersionRoComponent implements OnInit { - - @Input() projectId: number; @Input() charts: HelmChartSearchResultItem[]; constructor( diff --git a/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.html b/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.html index 1fda4aee0ca..38b0c3a269c 100644 --- a/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.html +++ b/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.html @@ -1,16 +1,17 @@ - + {{'PROJECT.NAME' | translate}} {{'PROJECT.ACCESS_LEVEL' | translate}} {{'PROJECT.REPO_COUNT'| translate}} {{'PROJECT.CREATION_TIME' | translate}} - {{p.name}} + {{p.name}} {{ (p.metadata.public === 'true' ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} {{p.repo_count}} {{p.creation_time | harborDatetime: 'short'}} + {{'PROJECT.NO_PROJECT' | translate }} {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} {{projects?.length}} {{'PROJECT.ITEMS' | translate}} - \ No newline at end of file + diff --git a/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.spec.ts b/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.spec.ts index 451e65e9784..20f87392e1d 100644 --- a/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.spec.ts +++ b/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.spec.ts @@ -1,47 +1,50 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ListProjectROComponent } from './list-project-ro.component'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { ClarityModule } from '@clr/angular'; -import { FormsModule } from '@angular/forms'; -import { RouterTestingModule } from '@angular/router/testing'; -import { of } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { SearchTriggerService } from '../global-search/search-trigger.service'; +import { SharedTestingModule } from "../../shared.module"; +import { Project } from "../../../../../ng-swagger-gen/models/project"; +import { Component } from '@angular/core'; + +// mock a TestHostComponent for ListProjectROComponent +@Component({ + template: ` + + ` +}) +class TestHostComponent { + projects: Project[] = []; +} describe('ListProjectROComponent', () => { - let component: ListProjectROComponent; - let fixture: ComponentFixture; - const mockSearchTriggerService = { - closeSearch: () => { } - }; + let component: TestHostComponent; + let fixture: ComponentFixture; + const mockedProjects: Project[] = [ + { + chart_count: 0, + name: "test1", + metadata: {}, + project_id: 1, + repo_count: 1 + }, + { + chart_count: 0, + name: "test2", + metadata: {}, + project_id: 2, + repo_count: 1 + } + ]; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ], imports: [ - BrowserAnimationsModule, - ClarityModule, - TranslateModule.forRoot(), - FormsModule, - RouterTestingModule, - NoopAnimationsModule, - HttpClientTestingModule + SharedTestingModule ], - declarations: [ListProjectROComponent], - providers: [ - TranslateService, - { provide: SearchTriggerService, useValue: mockSearchTriggerService } - - ] - }) - .compileComponents(); + declarations: [ListProjectROComponent, + TestHostComponent], + }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(ListProjectROComponent); + fixture = TestBed.createComponent(TestHostComponent); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -49,4 +52,12 @@ describe('ListProjectROComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should render project list', async () => { + component.projects = mockedProjects; + fixture.detectChanges(); + await fixture.whenStable(); + const rows = fixture.nativeElement.querySelectorAll('clr-dg-row'); + expect(rows.length).toEqual(2); + }); }); diff --git a/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.ts b/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.ts index 652eda1f54f..75cb2a6ba40 100644 --- a/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.ts +++ b/src/portal/src/app/shared/components/list-project-ro/list-project-ro.component.ts @@ -11,35 +11,21 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import { Component, EventEmitter, Output, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import { Router } from '@angular/router'; -import { State } from '../../services/interface'; - -import { SearchTriggerService } from '../global-search/search-trigger.service'; -import { Project } from '../../../base/project/project'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { Project } from "../../../../../ng-swagger-gen/models/project"; @Component({ - selector: 'list-project-ro', - templateUrl: 'list-project-ro.component.html', - changeDetection: ChangeDetectionStrategy.OnPush + selector: 'list-project-ro', + templateUrl: 'list-project-ro.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) export class ListProjectROComponent { - @Input() projects: Project[]; - @Output() paginate = new EventEmitter(); - - - constructor( - private searchTrigger: SearchTriggerService, - private router: Router) {} - - goToLink(proId: number): void { - this.searchTrigger.closeSearch(true); + @Input() projects: Project[]; - let linkUrl = ['harbor', 'projects', proId, 'repositories']; - this.router.navigate(linkUrl); - } + constructor() { + } - refresh(state: State) { - this.paginate.emit(state); - } + getLink(proId: number) { + return `/harbor/projects/${proId}/repositories`; + } } diff --git a/src/portal/src/app/shared/components/list-repository-ro/list-repository-ro.component.html b/src/portal/src/app/shared/components/list-repository-ro/list-repository-ro.component.html index 03705da803a..84964ca0d5e 100644 --- a/src/portal/src/app/shared/components/list-repository-ro/list-repository-ro.component.html +++ b/src/portal/src/app/shared/components/list-repository-ro/list-repository-ro.component.html @@ -1,14 +1,15 @@ - + {{'REPOSITORY.NAME' | translate}} {{'REPOSITORY.ARTIFACTS_COUNT' | translate}} {{'REPOSITORY.PULL_COUNT' | translate}} - {{r.name || r.repository_name}} + {{r.name || r.repository_name}} {{r.artifact_count}} {{r.pull_count}} + {{'REPOSITORY.PLACEHOLDER' | translate }} {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPOSITORY.OF' | translate}} {{repositories?.length }} {{'REPOSITORY.ITEMS' | translate}} - \ No newline at end of file + diff --git a/src/portal/src/app/shared/components/list-repository-ro/list-repository-ro.component.ts b/src/portal/src/app/shared/components/list-repository-ro/list-repository-ro.component.ts index 1e6628bcf56..bb045b8c0cb 100644 --- a/src/portal/src/app/shared/components/list-repository-ro/list-repository-ro.component.ts +++ b/src/portal/src/app/shared/components/list-repository-ro/list-repository-ro.component.ts @@ -1,5 +1,3 @@ - -import {filter} from 'rxjs/operators'; // Copyright (c) 2017 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,72 +11,39 @@ import {filter} from 'rxjs/operators'; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, Output, OnDestroy, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef, OnInit } from '@angular/core'; -import { Router, NavigationEnd } from '@angular/router'; -import { State } from '../../services/interface'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; import { Repository } from '../../../../../ng-swagger-gen/models/repository'; - import { SearchTriggerService } from '../global-search/search-trigger.service'; -import {Subscription} from "rxjs"; import { SessionService } from "../../services/session.service"; import { UN_LOGGED_PARAM } from "../../../account/sign-in/sign-in.service"; + const YES: string = 'yes'; + @Component({ selector: 'list-repository-ro', templateUrl: 'list-repository-ro.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class ListRepositoryROComponent implements OnInit, OnDestroy { +export class ListRepositoryROComponent { - @Input() projectId: number; @Input() repositories: Repository[]; - @Output() paginate = new EventEmitter(); - - routerSubscription: Subscription; - constructor( - private router: Router, - private searchTrigger: SearchTriggerService, - private ref: ChangeDetectorRef, - private sessionService: SessionService) { - this.router.routeReuseStrategy.shouldReuseRoute = function() { - return false; - }; - this.routerSubscription = this.router.events.pipe(filter(event => event instanceof NavigationEnd)) - .subscribe((event) => { - // trick the Router into believing it's last link wasn't previously loaded - this.router.navigated = false; - // if you need to scroll back to top, here is the right place - window.scrollTo(0, 0); - }); - } - - ngOnInit(): void { - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 1000); + private router: Router, + private searchTrigger: SearchTriggerService, + private sessionService: SessionService) { } - ngOnDestroy(): void { - this.routerSubscription.unsubscribe(); - } - - refresh(state: State) { - if (this.repositories) { - this.paginate.emit(state); - } - } - - public gotoLink(projectId: number, repoName: string): void { - this.searchTrigger.closeSearch(true); + getLink(projectId: number, repoName: string) { let projectName = repoName.split('/')[0]; let repositorieName = projectName ? repoName.substr(projectName.length + 1) : repoName; - let linkUrl = ['harbor', 'projects', projectId, 'repositories', repositorieName ]; + return `/harbor/projects/${projectId}/repositories/${repositorieName}`; + } + getQueryParams() { if (this.sessionService.getCurrentUser()) { - this.router.navigate(linkUrl); - } else {// if not logged in and it's a public project, add param 'publicAndNotLogged' - this.router.navigate(linkUrl, {queryParams: {[UN_LOGGED_PARAM]: YES}}); + return null; } + return {[UN_LOGGED_PARAM]: YES}; } - } diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index dc45adb4f2e..267cb1c8915 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -254,7 +254,8 @@ "PROXY_CACHE": "Proxy Cache", "PROXY_CACHE_TOOLTIP": "Die Aktivierung der Funktion erlaubt es dem Projekt, als Cache für eine andere Registry Instanz zu dienen. Harbor unterstützt die Proxy Funktion nur für DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay und Google GCR.", "ENDPOINT": "Endpunkt", - "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpunkt" + "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpunkt", + "NO_PROJECT": "We couldn't find any projects" }, "PROJECT_DETAIL": { "SUMMARY": "Zusammenfassung", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 43f6e46f6b6..a9c8e44da82 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -254,7 +254,8 @@ "PROXY_CACHE": "Proxy Cache", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay and Google GCR registries.", "ENDPOINT": "Endpoint", - "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" + "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", + "NO_PROJECT": "We couldn't find any projects" }, "PROJECT_DETAIL": { "SUMMARY": "Summary", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 47e213db34c..b31f2a6472f 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -255,7 +255,8 @@ "PROXY_CACHE": "Proxy Cache", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay and Google GCR registries.", "ENDPOINT": "Endpoint", - "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" + "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", + "NO_PROJECT": "We couldn't find any projects" }, "PROJECT_DETAIL": { "SUMMARY": "Summary", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 9bee5f2d002..5f8e508da24 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -248,7 +248,8 @@ "PROXY_CACHE": "Proxy Cache", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay and Google GCR registries.", "ENDPOINT": "Endpoint", - "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" + "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", + "NO_PROJECT": "We couldn't find any projects" }, "PROJECT_DETAIL": { "SUMMARY": "Summary", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 8bd6f59e6d6..4060daee11e 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -252,7 +252,8 @@ "PROXY_CACHE": "Proxy Cache", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay and Google GCR registries.", "ENDPOINT": "Endpoint", - "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" + "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", + "NO_PROJECT": "We couldn't find any projects" }, "PROJECT_DETAIL": { "SUMMARY": "Summary", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 07a6f1b42d9..65ec8514f01 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -254,7 +254,8 @@ "PROXY_CACHE": "Proxy Cache", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay and Google GCR registries.", "ENDPOINT": "Endpoint", - "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" + "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", + "NO_PROJECT": "We couldn't find any projects" }, "PROJECT_DETAIL": { "SUMMARY": "Özet", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 633a75e0ae5..ae4bc4f1b57 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -253,7 +253,8 @@ "PROXY_CACHE": "镜像代理", "PROXY_CACHE_TOOLTIP": "开启此项,以使得该项目成为目标仓库的镜像代理.仅支持 DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay 和 Google GCR 类型的仓库", "ENDPOINT": "地址", - "PROXY_CACHE_ENDPOINT": "镜像代理地址" + "PROXY_CACHE_ENDPOINT": "镜像代理地址", + "NO_PROJECT": "未发现任何项目" }, "PROJECT_DETAIL": { "SUMMARY": "概要", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 90d1e274315..d6fc4ef49e9 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -251,7 +251,8 @@ "PROXY_CACHE": "Proxy Cache", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay and Google GCR registries.", "ENDPOINT": "Endpoint", - "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" + "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", + "NO_PROJECT": "We couldn't find any projects" }, "PROJECT_DETAIL":{ "SUMMARY": "概要", From 4e998b7dce77133c464c71d1810c5d0eb7c351d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E4=B8=96=E5=86=9B?= <30999793+AllForNothing@users.noreply.github.com> Date: Tue, 24 Aug 2021 17:05:13 +0800 Subject: [PATCH 007/135] Fix chart download issue (#15472) Signed-off-by: AllForNothing --- .../helm-chart/helm-chart-detail/helm-chart.service.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/portal/src/app/base/project/helm-chart/helm-chart-detail/helm-chart.service.ts b/src/portal/src/app/base/project/helm-chart/helm-chart-detail/helm-chart.service.ts index 9aee0c1a971..ca3415561fa 100644 --- a/src/portal/src/app/base/project/helm-chart/helm-chart-detail/helm-chart.service.ts +++ b/src/portal/src/app/base/project/helm-chart/helm-chart-detail/helm-chart.service.ts @@ -169,9 +169,9 @@ export class HelmChartDefaultService extends HelmChartService { let chartFileRegexPattern = new RegExp('^http.*/chartrepo/(.*)'); if (chartFileRegexPattern.test(filename)) { let match = filename.match('^http.*/chartrepo/(.*)'); - url = `${V1_BASE_HREF + "/chartrepo"}/${match[1]}`; + url = `${DOWNLOAD_CHART_ENDPOINT}/${match[1]}`; } else { - url = `${V1_BASE_HREF + "/chartrepo"}/${projectName}/${filename}`; + url = `${DOWNLOAD_CHART_ENDPOINT}/${projectName}/${filename}`; } return this.http.get(url, { responseType: 'blob', @@ -209,3 +209,6 @@ export class HelmChartDefaultService extends HelmChartService { .pipe(catchError(this.handleErrorObservable)); } } + + +export const DOWNLOAD_CHART_ENDPOINT: string = "/chartrepo"; From 9aab74d38260c99f6f1a1913397049868c4ee126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E4=B8=96=E5=86=9B?= <30999793+AllForNothing@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:24:22 +0800 Subject: [PATCH 008/135] Correct clrDgTotalItems for tag-retention-tasks component (#15492) Signed-off-by: AllForNothing --- .../tag-retention-tasks/tag-retention-tasks.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/portal/src/app/base/project/tag-feature-integration/tag-retention/tag-retention-tasks/tag-retention-tasks/tag-retention-tasks.component.html b/src/portal/src/app/base/project/tag-feature-integration/tag-retention/tag-retention-tasks/tag-retention-tasks/tag-retention-tasks.component.html index 0be74eee6f1..71e1571f264 100644 --- a/src/portal/src/app/base/project/tag-feature-integration/tag-retention/tag-retention-tasks/tag-retention-tasks/tag-retention-tasks.component.html +++ b/src/portal/src/app/base/project/tag-feature-integration/tag-retention/tag-retention-tasks/tag-retention-tasks/tag-retention-tasks.component.html @@ -24,6 +24,6 @@ {{innerPagination.lastItem + 1 }} {{'ROBOT_ACCOUNT.OF' | translate}} {{ total }} {{'ROBOT_ACCOUNT.ITEMS' | translate}} - + From b58158c30f97204f180be740bbb16dcab98ab237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E4=B8=96=E5=86=9B?= <30999793+AllForNothing@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:24:45 +0800 Subject: [PATCH 009/135] Fix some UI bugs (#15486) Signed-off-by: AllForNothing --- .../create-edit-endpoint/create-edit-endpoint.component.ts | 5 ++++- .../create-edit-rule/create-edit-rule.component.ts | 3 ++- .../src/app/base/project/summary/summary.component.html | 2 +- .../src/app/base/project/summary/summary.component.spec.ts | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts b/src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts index b83aed9f4eb..da0527be2d0 100644 --- a/src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts +++ b/src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts @@ -214,7 +214,10 @@ export class CreateEditEndpointComponent this.endpointService.getEndpoint(targetId).subscribe( target => { this.target = target; - this.urlDisabled = this.target.type === 'docker-hub'; + this.urlDisabled = this.adapterInfo && + this.adapterInfo[this.target.type] && + this.adapterInfo[this.target.type].endpoint_pattern && + this.adapterInfo[this.target.type].endpoint_pattern.endpoint_type === FIXED_PATTERN_TYPE; // Keep data cache this.initVal = clone(target); this.initVal.credential.access_secret = this.target.type === 'google-gcr' ? FAKE_JSON_KEY : FAKE_PASSWORD; diff --git a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts index 14459e5aa1e..94cfdff7b02 100644 --- a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts +++ b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts @@ -207,7 +207,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { get isValid() { if (this.ruleForm.controls["dest_namespace"].value) { - if (this.ruleForm.controls["dest_namespace"].invalid) { + if (this.ruleForm.controls["dest_namespace"].invalid) {this.ruleForm.get('trigger').get('trigger_settings').get('cron').value return false; } } @@ -567,6 +567,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { && this.ruleForm.get('trigger').get('trigger_settings').get('cron') && (this.ruleForm.get('trigger').get('trigger_settings').get('cron').touched || this.ruleForm.get('trigger').get('trigger_settings').get('cron').dirty) + && this.ruleForm.get('trigger').get('trigger_settings').get('cron').value && !cronRegex(this.ruleForm.get('trigger').get('trigger_settings').get('cron').value); } stickLabel(value, index) { diff --git a/src/portal/src/app/base/project/summary/summary.component.html b/src/portal/src/app/base/project/summary/summary.component.html index 2170a3fc195..3e85e1255f5 100644 --- a/src/portal/src/app/base/project/summary/summary.component.html +++ b/src/portal/src/app/base/project/summary/summary.component.html @@ -77,7 +77,7 @@

{{'SUMMARY.PROJECT_MEMBER' | translate}}

-
+
{{"PROJECT_DETAIL.HELMCHART" | translate}}
{{summaryInformation?.chart_count ? summaryInformation?.chart_count : 0}}
diff --git a/src/portal/src/app/base/project/summary/summary.component.spec.ts b/src/portal/src/app/base/project/summary/summary.component.spec.ts index 58865399508..80a752bb059 100644 --- a/src/portal/src/app/base/project/summary/summary.component.spec.ts +++ b/src/portal/src/app/base/project/summary/summary.component.spec.ts @@ -132,13 +132,13 @@ describe('SummaryComponent', () => { expect(container).toBeTruthy(); }); - it('should show three cards', async () => { + it('should show two cards', async () => { component.summaryInformation = mockedSummaryInformation; component.isCardView = true; component.hasReadChartPermission = true; fixture.detectChanges(); await fixture.whenStable(); const cards = fixture.nativeElement.querySelectorAll(".card"); - expect(cards.length).toEqual(3); + expect(cards.length).toEqual(2); }); }); From d482a0c323781260a2f3a39145edc08de024ee9e Mon Sep 17 00:00:00 2001 From: He Weiwei Date: Fri, 27 Aug 2021 17:28:33 +0800 Subject: [PATCH 010/135] fix: avoid panic in the RetryUntil (#15501) 1. Use jpillora/backoff to get the backoff to avoid the panic in RetryUntil. 2. Return with last err when retry timeout. Signed-off-by: He Weiwei --- src/controller/replication/execution.go | 3 +- src/controller/scan/base_controller.go | 4 +- src/go.mod | 1 + src/go.sum | 4 +- .../job/impl/gc/garbage_collection.go | 10 +- src/jobservice/job/impl/gc/util.go | 9 +- src/lib/cache/cache.go | 14 +- src/lib/{ => retry}/retry.go | 93 +++++------ src/lib/{ => retry}/retry_test.go | 21 ++- .../github.com/dgrijalva/jwt-go/.gitignore | 4 - .../github.com/dgrijalva/jwt-go/.travis.yml | 13 -- .../github.com/dgrijalva/jwt-go/LICENSE | 8 - .../dgrijalva/jwt-go/MIGRATION_GUIDE.md | 97 ------------ .../github.com/dgrijalva/jwt-go/README.md | 100 ------------ .../dgrijalva/jwt-go/VERSION_HISTORY.md | 118 -------------- .../github.com/dgrijalva/jwt-go/claims.go | 134 ---------------- src/vendor/github.com/dgrijalva/jwt-go/doc.go | 4 - .../github.com/dgrijalva/jwt-go/ecdsa.go | 148 ------------------ .../dgrijalva/jwt-go/ecdsa_utils.go | 67 -------- .../github.com/dgrijalva/jwt-go/errors.go | 59 ------- .../github.com/dgrijalva/jwt-go/hmac.go | 95 ----------- .../github.com/dgrijalva/jwt-go/map_claims.go | 94 ----------- .../github.com/dgrijalva/jwt-go/none.go | 52 ------ .../github.com/dgrijalva/jwt-go/parser.go | 148 ------------------ src/vendor/github.com/dgrijalva/jwt-go/rsa.go | 101 ------------ .../github.com/dgrijalva/jwt-go/rsa_pss.go | 126 --------------- .../github.com/dgrijalva/jwt-go/rsa_utils.go | 101 ------------ .../dgrijalva/jwt-go/signing_method.go | 35 ----- .../github.com/dgrijalva/jwt-go/token.go | 108 ------------- .../github.com/jpillora/backoff/LICENSE | 21 +++ .../github.com/jpillora/backoff/README.md | 119 ++++++++++++++ .../github.com/jpillora/backoff/backoff.go | 100 ++++++++++++ src/vendor/github.com/jpillora/backoff/go.mod | 3 + src/vendor/modules.txt | 5 +- 34 files changed, 327 insertions(+), 1692 deletions(-) rename src/lib/{ => retry}/retry.go (56%) rename src/lib/{ => retry}/retry_test.go (77%) delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/.gitignore delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/.travis.yml delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/LICENSE delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/README.md delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/claims.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/doc.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/ecdsa.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/errors.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/hmac.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/map_claims.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/none.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/parser.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/rsa.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/rsa_pss.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/signing_method.go delete mode 100644 src/vendor/github.com/dgrijalva/jwt-go/token.go create mode 100644 src/vendor/github.com/jpillora/backoff/LICENSE create mode 100644 src/vendor/github.com/jpillora/backoff/README.md create mode 100644 src/vendor/github.com/jpillora/backoff/backoff.go create mode 100644 src/vendor/github.com/jpillora/backoff/go.mod diff --git a/src/controller/replication/execution.go b/src/controller/replication/execution.go index f7d16c67bd5..ed672d257e0 100644 --- a/src/controller/replication/execution.go +++ b/src/controller/replication/execution.go @@ -27,6 +27,7 @@ import ( "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/lib/retry" "github.com/goharbor/harbor/src/pkg/reg" "github.com/goharbor/harbor/src/pkg/reg/model" "github.com/goharbor/harbor/src/pkg/replication" @@ -129,7 +130,7 @@ func (c *controller) Start(ctx context.Context, policy *replicationmodel.Policy, // as we start a new transaction in the goroutine, the execution record may not // be inserted yet, wait until it is ready before continue - if err := lib.RetryUntil(func() error { + if err := retry.Retry(func() error { _, err := c.execMgr.Get(ctx, id) return err }); err != nil { diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index 7d2b040675d..f26ff49c1f6 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -28,12 +28,12 @@ import ( sc "github.com/goharbor/harbor/src/controller/scanner" "github.com/goharbor/harbor/src/controller/tag" "github.com/goharbor/harbor/src/jobservice/job" - "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/lib/retry" allowlist "github.com/goharbor/harbor/src/pkg/allowlist/models" "github.com/goharbor/harbor/src/pkg/permission/types" "github.com/goharbor/harbor/src/pkg/robot/model" @@ -322,7 +322,7 @@ func (bc *basicController) ScanAll(ctx context.Context, trigger string, async bo if async { go func(ctx context.Context) { // if async, this is running in another goroutine ensure the execution exists in db - err := lib.RetryUntil(func() error { + err := retry.Retry(func() error { _, err := bc.execMgr.Get(ctx, executionID) return err }) diff --git a/src/go.mod b/src/go.mod index 2c4e2a95ee9..3b54ab7070b 100644 --- a/src/go.mod +++ b/src/go.mod @@ -49,6 +49,7 @@ require ( github.com/gorilla/mux v1.7.4 github.com/graph-gophers/dataloader v5.0.0+incompatible github.com/jinzhu/gorm v1.9.8 // indirect + github.com/jpillora/backoff v1.0.0 github.com/lib/pq v1.10.0 github.com/miekg/pkcs11 v0.0.0-20170220202408-7283ca79f35e // indirect github.com/ncw/swift v1.0.49 // indirect diff --git a/src/go.sum b/src/go.sum index 5577fe91210..ab4d68147f0 100644 --- a/src/go.sum +++ b/src/go.sum @@ -245,7 +245,6 @@ github.com/denverdino/aliyungo v0.0.0-20191227032621-df38c6fa730c/go.mod h1:dV8l github.com/dghubble/sling v1.1.0 h1:DLu20Bq2qsB9cI5Hldaxj+TMPEaPpPE8IR2kvD22Atg= github.com/dghubble/sling v1.1.0/go.mod h1:ZcPRuLm0qrcULW2gOrjXrAWgf76sahqSyxXyVOvkunE= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dhui/dktest v0.3.2 h1:nZSDcnkpbotzT/nEHNsO+JCKY8i1Qoki1AYOpeLRb6M= @@ -638,6 +637,8 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -1400,6 +1401,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= diff --git a/src/jobservice/job/impl/gc/garbage_collection.go b/src/jobservice/job/impl/gc/garbage_collection.go index 94ed29bff92..357b2114389 100644 --- a/src/jobservice/job/impl/gc/garbage_collection.go +++ b/src/jobservice/job/impl/gc/garbage_collection.go @@ -15,7 +15,6 @@ package gc import ( - "github.com/goharbor/harbor/src/lib" "os" "time" @@ -27,6 +26,7 @@ import ( "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/q" redislib "github.com/goharbor/harbor/src/lib/redis" + "github.com/goharbor/harbor/src/lib/retry" "github.com/goharbor/harbor/src/pkg/artifactrash" "github.com/goharbor/harbor/src/pkg/artifactrash/model" "github.com/goharbor/harbor/src/pkg/blob" @@ -271,11 +271,11 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error { } // for manifest, it has to delete the revisions folder of each repository gc.logger.Infof("delete manifest from storage: %s", blob.Digest) - if err := lib.RetryUntil(func() error { + if err := retry.Retry(func() error { return ignoreNotFound(func() error { return gc.registryCtlClient.DeleteManifest(art.RepositoryName, blob.Digest) }) - }, lib.RetryCallback(func(err error, sleep time.Duration) { + }, retry.Callback(func(err error, sleep time.Duration) { gc.logger.Infof("failed to exec DeleteManifest retry after %s : %v", sleep, err) })); err != nil { if err := ignoreNotFound(func() error { @@ -299,11 +299,11 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error { // for the foreign layer, as it's not stored in the storage, no need to call the delete api and count size, but still have to delete the DB record. if !blob.IsForeignLayer() { gc.logger.Infof("delete blob from storage: %s", blob.Digest) - if err := lib.RetryUntil(func() error { + if err := retry.Retry(func() error { return ignoreNotFound(func() error { return gc.registryCtlClient.DeleteBlob(blob.Digest) }) - }, lib.RetryCallback(func(err error, sleep time.Duration) { + }, retry.Callback(func(err error, sleep time.Duration) { gc.logger.Infof("failed to exec DeleteBlob retry after %s : %v", sleep, err) })); err != nil { if err := ignoreNotFound(func() error { diff --git a/src/jobservice/job/impl/gc/util.go b/src/jobservice/job/impl/gc/util.go index e6466f1e029..3e2da0b428d 100644 --- a/src/jobservice/job/impl/gc/util.go +++ b/src/jobservice/job/impl/gc/util.go @@ -2,11 +2,12 @@ package gc import ( "fmt" - "github.com/goharbor/harbor/src/lib" + "time" + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/retry" "github.com/goharbor/harbor/src/pkg/registry" "github.com/gomodule/redigo/redis" - "time" ) // delKeys ... @@ -52,9 +53,9 @@ func v2DeleteManifest(repository, digest string) error { if !exist { return nil } - return lib.RetryUntil(func() error { + return retry.Retry(func() error { return registry.Cli.DeleteManifest(repository, digest) - }, lib.RetryCallback(func(err error, sleep time.Duration) { + }, retry.Callback(func(err error, sleep time.Duration) { fmt.Printf("failed to exec DeleteManifest retry after %s : %v\n", sleep, err) })) } diff --git a/src/lib/cache/cache.go b/src/lib/cache/cache.go index efc91dfdfac..a22a3a3f5ac 100644 --- a/src/lib/cache/cache.go +++ b/src/lib/cache/cache.go @@ -21,8 +21,8 @@ import ( "sync" "time" - "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/lib/retry" ) const ( @@ -102,16 +102,16 @@ func Initialize(typ, addr string) error { redactedAddr = redacted(u) } - options := []lib.RetryOption{ - lib.RetryInitialInterval(time.Millisecond * 500), - lib.RetryMaxInterval(time.Second * 10), - lib.RetryTimeout(time.Minute), - lib.RetryCallback(func(err error, sleep time.Duration) { + options := []retry.Option{ + retry.InitialInterval(time.Millisecond * 500), + retry.MaxInterval(time.Second * 10), + retry.Timeout(time.Minute), + retry.Callback(func(err error, sleep time.Duration) { log.Errorf("failed to ping %s, retry after %s : %v", redactedAddr, sleep, err) }), } - if err := lib.RetryUntil(c.Ping, options...); err != nil { + if err := retry.Retry(c.Ping, options...); err != nil { return err } diff --git a/src/lib/retry.go b/src/lib/retry/retry.go similarity index 56% rename from src/lib/retry.go rename to src/lib/retry/retry.go index 8a1280d4f0d..f9fb0672759 100644 --- a/src/lib/retry.go +++ b/src/lib/retry/retry.go @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package lib +package retry import ( - "errors" - "math" "math/rand" "time" + + "github.com/goharbor/harbor/src/lib/errors" + "github.com/jpillora/backoff" ) func init() { @@ -30,51 +31,51 @@ var ( ErrRetryTimeout = errors.New("retry timeout") ) -// RetryOptions options for the retry functions -type RetryOptions struct { +// Options options for the retry functions +type Options struct { InitialInterval time.Duration // the initial interval for retring after failure, default 100 milliseconds MaxInterval time.Duration // the max interval for retring after failure, default 1 second Timeout time.Duration // the total time before returning if something is wrong, default 1 minute Callback func(err error, sleep time.Duration) // the callback function for Retry when the f called failed } -// RetryOption ... -type RetryOption func(*RetryOptions) +// Option ... +type Option func(*Options) -// RetryInitialInterval set initial interval -func RetryInitialInterval(initial time.Duration) RetryOption { - return func(opts *RetryOptions) { +// InitialInterval set initial interval +func InitialInterval(initial time.Duration) Option { + return func(opts *Options) { opts.InitialInterval = initial } } -// RetryMaxInterval set max interval -func RetryMaxInterval(max time.Duration) RetryOption { - return func(opts *RetryOptions) { +// MaxInterval set max interval +func MaxInterval(max time.Duration) Option { + return func(opts *Options) { opts.MaxInterval = max } } -// RetryTimeout set timeout interval -func RetryTimeout(timeout time.Duration) RetryOption { - return func(opts *RetryOptions) { +// Timeout set timeout interval +func Timeout(timeout time.Duration) Option { + return func(opts *Options) { opts.Timeout = timeout } } -// RetryCallback set callback -func RetryCallback(callback func(err error, sleep time.Duration)) RetryOption { - return func(opts *RetryOptions) { +// Callback set callback +func Callback(callback func(err error, sleep time.Duration)) Option { + return func(opts *Options) { opts.Callback = callback } } -// RetryUntil retry until f run successfully or timeout +// Retry retry until f run successfully or timeout // // NOTE: This function will use exponential backoff and jitter for retrying, see // https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for more information -func RetryUntil(f func() error, options ...RetryOption) error { - opts := &RetryOptions{} +func Retry(f func() error, options ...Option) error { + opts := &Options{} for _, o := range options { o(opts) @@ -92,42 +93,32 @@ func RetryUntil(f func() error, options ...RetryOption) error { opts.Timeout = time.Minute } + b := &backoff.Backoff{ + Min: opts.InitialInterval, + Max: opts.MaxInterval, + Factor: 2, + Jitter: true, + } + + var err error + timeout := time.After(opts.Timeout) - for attempt := 1; ; attempt++ { + for { select { case <-timeout: - return ErrRetryTimeout + return errors.New(ErrRetryTimeout).WithCause(err) default: - if err := f(); err != nil { - sleep := getBackoff(attempt, opts.InitialInterval, opts.MaxInterval, true) - if opts.Callback != nil { - opts.Callback(err, sleep) - } - - time.Sleep(sleep) - } else { + err = f() + if err == nil { return nil } - } - } -} -func getBackoff(attempt int, initialInterval, maxInterval time.Duration, equalJitter bool) time.Duration { - max := float64(maxInterval) - base := float64(initialInterval) - - dur := base * math.Pow(2, float64(attempt)) - if equalJitter { - dur = dur/2 + float64(rand.Int63n(int64(dur))/2) - } - - if dur < base { - dur = base - } + sleep := b.Duration() + if opts.Callback != nil { + opts.Callback(err, sleep) + } - if dur > max { - dur = max + time.Sleep(sleep) + } } - - return time.Duration(dur) } diff --git a/src/lib/retry_test.go b/src/lib/retry/retry_test.go similarity index 77% rename from src/lib/retry_test.go rename to src/lib/retry/retry_test.go index b2be2cbe458..7db179deb76 100644 --- a/src/lib/retry_test.go +++ b/src/lib/retry/retry_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package lib +package retry import ( "fmt" @@ -30,7 +30,7 @@ func TestRetryUntil(t *testing.T) { i++ return fmt.Errorf("failed") } - assert.Error(RetryUntil(f1, RetryInitialInterval(time.Second), RetryMaxInterval(time.Second), RetryTimeout(time.Second*5))) + assert.Error(Retry(f1, InitialInterval(time.Second), MaxInterval(time.Second), Timeout(time.Second*5))) // f1 called time 0s - sleep - 1s - sleep - 2s - sleep - 3s - sleep - 4s - sleep - 5s // i after f1 called 1 2 3 4 5 6 // the i may be 5 or 6 depend on timeout or default which is seleted by the select statement @@ -39,7 +39,7 @@ func TestRetryUntil(t *testing.T) { f2 := func() error { return nil } - assert.Nil(RetryUntil(f2)) + assert.Nil(Retry(f2)) i = 0 f3 := func() error { @@ -52,13 +52,20 @@ func TestRetryUntil(t *testing.T) { } return nil } - assert.Nil(RetryUntil(f3)) + assert.Nil(Retry(f3)) - RetryUntil( + Retry( f1, - RetryTimeout(time.Second*5), - RetryCallback(func(err error, sleep time.Duration) { + Timeout(time.Second*5), + Callback(func(err error, sleep time.Duration) { fmt.Printf("failed to exec f1 retry after %s : %v\n", sleep, err) }), ) + + err := Retry(func() error { + return fmt.Errorf("always failed") + }) + + assert.Error(err) + assert.Equal("retry timeout: always failed", err.Error()) } diff --git a/src/vendor/github.com/dgrijalva/jwt-go/.gitignore b/src/vendor/github.com/dgrijalva/jwt-go/.gitignore deleted file mode 100644 index 80bed650ec0..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.DS_Store -bin - - diff --git a/src/vendor/github.com/dgrijalva/jwt-go/.travis.yml b/src/vendor/github.com/dgrijalva/jwt-go/.travis.yml deleted file mode 100644 index 1027f56cd94..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: go - -script: - - go vet ./... - - go test -v ./... - -go: - - 1.3 - - 1.4 - - 1.5 - - 1.6 - - 1.7 - - tip diff --git a/src/vendor/github.com/dgrijalva/jwt-go/LICENSE b/src/vendor/github.com/dgrijalva/jwt-go/LICENSE deleted file mode 100644 index df83a9c2f01..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -Copyright (c) 2012 Dave Grijalva - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/src/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md b/src/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md deleted file mode 100644 index 7fc1f793cbc..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md +++ /dev/null @@ -1,97 +0,0 @@ -## Migration Guide from v2 -> v3 - -Version 3 adds several new, frequently requested features. To do so, it introduces a few breaking changes. We've worked to keep these as minimal as possible. This guide explains the breaking changes and how you can quickly update your code. - -### `Token.Claims` is now an interface type - -The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`. We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`. - -`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior. It is the default claims type when using `Parse`. The usage is unchanged except you must type cast the claims property. - -The old example for parsing a token looked like this.. - -```go - if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil { - fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) - } -``` - -is now directly mapped to... - -```go - if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil { - claims := token.Claims.(jwt.MapClaims) - fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) - } -``` - -`StandardClaims` is designed to be embedded in your custom type. You can supply a custom claims type with the new `ParseWithClaims` function. Here's an example of using a custom claims type. - -```go - type MyCustomClaims struct { - User string - *StandardClaims - } - - if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil { - claims := token.Claims.(*MyCustomClaims) - fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt) - } -``` - -### `ParseFromRequest` has been moved - -To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`. The method signatues have also been augmented to receive a new argument: `Extractor`. - -`Extractors` do the work of picking the token string out of a request. The interface is simple and composable. - -This simple parsing example: - -```go - if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil { - fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) - } -``` - -is directly mapped to: - -```go - if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil { - claims := token.Claims.(jwt.MapClaims) - fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) - } -``` - -There are several concrete `Extractor` types provided for your convenience: - -* `HeaderExtractor` will search a list of headers until one contains content. -* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content. -* `MultiExtractor` will try a list of `Extractors` in order until one returns content. -* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token. -* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument -* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed. A simple example is stripping the `Bearer ` text from a header - - -### RSA signing methods no longer accept `[]byte` keys - -Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse. - -To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`. These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types. - -```go - func keyLookupFunc(*Token) (interface{}, error) { - // Don't forget to validate the alg is what you expect: - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - - // Look up key - key, err := lookupPublicKey(token.Header["kid"]) - if err != nil { - return nil, err - } - - // Unpack key from PEM encoded PKCS8 - return jwt.ParseRSAPublicKeyFromPEM(key) - } -``` diff --git a/src/vendor/github.com/dgrijalva/jwt-go/README.md b/src/vendor/github.com/dgrijalva/jwt-go/README.md deleted file mode 100644 index d358d881b8d..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# jwt-go - -[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go) -[![GoDoc](https://godoc.org/github.com/dgrijalva/jwt-go?status.svg)](https://godoc.org/github.com/dgrijalva/jwt-go) - -A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) - -**NEW VERSION COMING:** There have been a lot of improvements suggested since the version 3.0.0 released in 2016. I'm working now on cutting two different releases: 3.2.0 will contain any non-breaking changes or enhancements. 4.0.0 will follow shortly which will include breaking changes. See the 4.0.0 milestone to get an idea of what's coming. If you have other ideas, or would like to participate in 4.0.0, now's the time. If you depend on this library and don't want to be interrupted, I recommend you use your dependency mangement tool to pin to version 3. - -**SECURITY NOTICE:** Some older versions of Go have a security issue in the cryotp/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue #216 for more detail. - -**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided. - -## What the heck is a JWT? - -JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens. - -In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way. - -The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used. - -The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own. - -## What's in the box? - -This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own. - -## Examples - -See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage: - -* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac) -* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac) -* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples) - -## Extensions - -This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`. - -Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go - -## Compliance - -This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences: - -* In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key. - -## Project Status & Versioning - -This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason). - -This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases). - -While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v3`. It will do the right thing WRT semantic versioning. - -**BREAKING CHANGES:*** -* Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code. - -## Usage Tips - -### Signing vs Encryption - -A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data: - -* The author of the token was in the possession of the signing secret -* The data has not been modified since it was signed - -It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library. - -### Choosing a Signing Method - -There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric. - -Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation. - -Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification. - -### Signing Methods and Key Types - -Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones: - -* The [HMAC signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation -* The [RSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation -* The [ECDSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation - -### JWT and OAuth - -It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication. - -Without going too far down the rabbit hole, here's a description of the interaction of these technologies: - -* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth. -* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token. -* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL. - -## More - -Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). - -The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation. diff --git a/src/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md b/src/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md deleted file mode 100644 index 6370298313a..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md +++ /dev/null @@ -1,118 +0,0 @@ -## `jwt-go` Version History - -#### 3.2.0 - -* Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation -* HMAC signing method returns `ErrInvalidKeyType` instead of `ErrInvalidKey` where appropriate -* Added options to `request.ParseFromRequest`, which allows for an arbitrary list of modifiers to parsing behavior. Initial set include `WithClaims` and `WithParser`. Existing usage of this function will continue to work as before. -* Deprecated `ParseFromRequestWithClaims` to simplify API in the future. - -#### 3.1.0 - -* Improvements to `jwt` command line tool -* Added `SkipClaimsValidation` option to `Parser` -* Documentation updates - -#### 3.0.0 - -* **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code - * Dropped support for `[]byte` keys when using RSA signing methods. This convenience feature could contribute to security vulnerabilities involving mismatched key types with signing methods. - * `ParseFromRequest` has been moved to `request` subpackage and usage has changed - * The `Claims` property on `Token` is now type `Claims` instead of `map[string]interface{}`. The default value is type `MapClaims`, which is an alias to `map[string]interface{}`. This makes it possible to use a custom type when decoding claims. -* Other Additions and Changes - * Added `Claims` interface type to allow users to decode the claims into a custom type - * Added `ParseWithClaims`, which takes a third argument of type `Claims`. Use this function instead of `Parse` if you have a custom type you'd like to decode into. - * Dramatically improved the functionality and flexibility of `ParseFromRequest`, which is now in the `request` subpackage - * Added `ParseFromRequestWithClaims` which is the `FromRequest` equivalent of `ParseWithClaims` - * Added new interface type `Extractor`, which is used for extracting JWT strings from http requests. Used with `ParseFromRequest` and `ParseFromRequestWithClaims`. - * Added several new, more specific, validation errors to error type bitmask - * Moved examples from README to executable example files - * Signing method registry is now thread safe - * Added new property to `ValidationError`, which contains the raw error returned by calls made by parse/verify (such as those returned by keyfunc or json parser) - -#### 2.7.0 - -This will likely be the last backwards compatible release before 3.0.0, excluding essential bug fixes. - -* Added new option `-show` to the `jwt` command that will just output the decoded token without verifying -* Error text for expired tokens includes how long it's been expired -* Fixed incorrect error returned from `ParseRSAPublicKeyFromPEM` -* Documentation updates - -#### 2.6.0 - -* Exposed inner error within ValidationError -* Fixed validation errors when using UseJSONNumber flag -* Added several unit tests - -#### 2.5.0 - -* Added support for signing method none. You shouldn't use this. The API tries to make this clear. -* Updated/fixed some documentation -* Added more helpful error message when trying to parse tokens that begin with `BEARER ` - -#### 2.4.0 - -* Added new type, Parser, to allow for configuration of various parsing parameters - * You can now specify a list of valid signing methods. Anything outside this set will be rejected. - * You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON -* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go) -* Fixed some bugs with ECDSA parsing - -#### 2.3.0 - -* Added support for ECDSA signing methods -* Added support for RSA PSS signing methods (requires go v1.4) - -#### 2.2.0 - -* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic. - -#### 2.1.0 - -Backwards compatible API change that was missed in 2.0.0. - -* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte` - -#### 2.0.0 - -There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change. - -The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`. - -It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`. - -* **Compatibility Breaking Changes** - * `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct` - * `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct` - * `KeyFunc` now returns `interface{}` instead of `[]byte` - * `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key - * `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key -* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type. - * Added public package global `SigningMethodHS256` - * Added public package global `SigningMethodHS384` - * Added public package global `SigningMethodHS512` -* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type. - * Added public package global `SigningMethodRS256` - * Added public package global `SigningMethodRS384` - * Added public package global `SigningMethodRS512` -* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged. -* Refactored the RSA implementation to be easier to read -* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM` - -#### 1.0.2 - -* Fixed bug in parsing public keys from certificates -* Added more tests around the parsing of keys for RS256 -* Code refactoring in RS256 implementation. No functional changes - -#### 1.0.1 - -* Fixed panic if RS256 signing method was passed an invalid key - -#### 1.0.0 - -* First versioned release -* API stabilized -* Supports creating, signing, parsing, and validating JWT tokens -* Supports RS256 and HS256 signing methods \ No newline at end of file diff --git a/src/vendor/github.com/dgrijalva/jwt-go/claims.go b/src/vendor/github.com/dgrijalva/jwt-go/claims.go deleted file mode 100644 index f0228f02e03..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/claims.go +++ /dev/null @@ -1,134 +0,0 @@ -package jwt - -import ( - "crypto/subtle" - "fmt" - "time" -) - -// For a type to be a Claims object, it must just have a Valid method that determines -// if the token is invalid for any supported reason -type Claims interface { - Valid() error -} - -// Structured version of Claims Section, as referenced at -// https://tools.ietf.org/html/rfc7519#section-4.1 -// See examples for how to use this with your own claim types -type StandardClaims struct { - Audience string `json:"aud,omitempty"` - ExpiresAt int64 `json:"exp,omitempty"` - Id string `json:"jti,omitempty"` - IssuedAt int64 `json:"iat,omitempty"` - Issuer string `json:"iss,omitempty"` - NotBefore int64 `json:"nbf,omitempty"` - Subject string `json:"sub,omitempty"` -} - -// Validates time based claims "exp, iat, nbf". -// There is no accounting for clock skew. -// As well, if any of the above claims are not in the token, it will still -// be considered a valid claim. -func (c StandardClaims) Valid() error { - vErr := new(ValidationError) - now := TimeFunc().Unix() - - // The claims below are optional, by default, so if they are set to the - // default value in Go, let's not fail the verification for them. - if c.VerifyExpiresAt(now, false) == false { - delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) - vErr.Inner = fmt.Errorf("token is expired by %v", delta) - vErr.Errors |= ValidationErrorExpired - } - - if c.VerifyIssuedAt(now, false) == false { - vErr.Inner = fmt.Errorf("Token used before issued") - vErr.Errors |= ValidationErrorIssuedAt - } - - if c.VerifyNotBefore(now, false) == false { - vErr.Inner = fmt.Errorf("token is not valid yet") - vErr.Errors |= ValidationErrorNotValidYet - } - - if vErr.valid() { - return nil - } - - return vErr -} - -// Compares the aud claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { - return verifyAud(c.Audience, cmp, req) -} - -// Compares the exp claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool { - return verifyExp(c.ExpiresAt, cmp, req) -} - -// Compares the iat claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { - return verifyIat(c.IssuedAt, cmp, req) -} - -// Compares the iss claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool { - return verifyIss(c.Issuer, cmp, req) -} - -// Compares the nbf claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { - return verifyNbf(c.NotBefore, cmp, req) -} - -// ----- helpers - -func verifyAud(aud string, cmp string, required bool) bool { - if aud == "" { - return !required - } - if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 { - return true - } else { - return false - } -} - -func verifyExp(exp int64, now int64, required bool) bool { - if exp == 0 { - return !required - } - return now <= exp -} - -func verifyIat(iat int64, now int64, required bool) bool { - if iat == 0 { - return !required - } - return now >= iat -} - -func verifyIss(iss string, cmp string, required bool) bool { - if iss == "" { - return !required - } - if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 { - return true - } else { - return false - } -} - -func verifyNbf(nbf int64, now int64, required bool) bool { - if nbf == 0 { - return !required - } - return now >= nbf -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/doc.go b/src/vendor/github.com/dgrijalva/jwt-go/doc.go deleted file mode 100644 index a86dc1a3b34..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html -// -// See README.md for more info. -package jwt diff --git a/src/vendor/github.com/dgrijalva/jwt-go/ecdsa.go b/src/vendor/github.com/dgrijalva/jwt-go/ecdsa.go deleted file mode 100644 index f977381240e..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/ecdsa.go +++ /dev/null @@ -1,148 +0,0 @@ -package jwt - -import ( - "crypto" - "crypto/ecdsa" - "crypto/rand" - "errors" - "math/big" -) - -var ( - // Sadly this is missing from crypto/ecdsa compared to crypto/rsa - ErrECDSAVerification = errors.New("crypto/ecdsa: verification error") -) - -// Implements the ECDSA family of signing methods signing methods -// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification -type SigningMethodECDSA struct { - Name string - Hash crypto.Hash - KeySize int - CurveBits int -} - -// Specific instances for EC256 and company -var ( - SigningMethodES256 *SigningMethodECDSA - SigningMethodES384 *SigningMethodECDSA - SigningMethodES512 *SigningMethodECDSA -) - -func init() { - // ES256 - SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256} - RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod { - return SigningMethodES256 - }) - - // ES384 - SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384} - RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod { - return SigningMethodES384 - }) - - // ES512 - SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521} - RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod { - return SigningMethodES512 - }) -} - -func (m *SigningMethodECDSA) Alg() string { - return m.Name -} - -// Implements the Verify method from SigningMethod -// For this verify method, key must be an ecdsa.PublicKey struct -func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error { - var err error - - // Decode the signature - var sig []byte - if sig, err = DecodeSegment(signature); err != nil { - return err - } - - // Get the key - var ecdsaKey *ecdsa.PublicKey - switch k := key.(type) { - case *ecdsa.PublicKey: - ecdsaKey = k - default: - return ErrInvalidKeyType - } - - if len(sig) != 2*m.KeySize { - return ErrECDSAVerification - } - - r := big.NewInt(0).SetBytes(sig[:m.KeySize]) - s := big.NewInt(0).SetBytes(sig[m.KeySize:]) - - // Create hasher - if !m.Hash.Available() { - return ErrHashUnavailable - } - hasher := m.Hash.New() - hasher.Write([]byte(signingString)) - - // Verify the signature - if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true { - return nil - } else { - return ErrECDSAVerification - } -} - -// Implements the Sign method from SigningMethod -// For this signing method, key must be an ecdsa.PrivateKey struct -func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) { - // Get the key - var ecdsaKey *ecdsa.PrivateKey - switch k := key.(type) { - case *ecdsa.PrivateKey: - ecdsaKey = k - default: - return "", ErrInvalidKeyType - } - - // Create the hasher - if !m.Hash.Available() { - return "", ErrHashUnavailable - } - - hasher := m.Hash.New() - hasher.Write([]byte(signingString)) - - // Sign the string and return r, s - if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil { - curveBits := ecdsaKey.Curve.Params().BitSize - - if m.CurveBits != curveBits { - return "", ErrInvalidKey - } - - keyBytes := curveBits / 8 - if curveBits%8 > 0 { - keyBytes += 1 - } - - // We serialize the outpus (r and s) into big-endian byte arrays and pad - // them with zeros on the left to make sure the sizes work out. Both arrays - // must be keyBytes long, and the output must be 2*keyBytes long. - rBytes := r.Bytes() - rBytesPadded := make([]byte, keyBytes) - copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) - - sBytes := s.Bytes() - sBytesPadded := make([]byte, keyBytes) - copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) - - out := append(rBytesPadded, sBytesPadded...) - - return EncodeSegment(out), nil - } else { - return "", err - } -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go b/src/vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go deleted file mode 100644 index d19624b7264..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go +++ /dev/null @@ -1,67 +0,0 @@ -package jwt - -import ( - "crypto/ecdsa" - "crypto/x509" - "encoding/pem" - "errors" -) - -var ( - ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key") - ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key") -) - -// Parse PEM encoded Elliptic Curve Private Key Structure -func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { - var err error - - // Parse PEM block - var block *pem.Block - if block, _ = pem.Decode(key); block == nil { - return nil, ErrKeyMustBePEMEncoded - } - - // Parse the key - var parsedKey interface{} - if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil { - return nil, err - } - - var pkey *ecdsa.PrivateKey - var ok bool - if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { - return nil, ErrNotECPrivateKey - } - - return pkey, nil -} - -// Parse PEM encoded PKCS1 or PKCS8 public key -func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { - var err error - - // Parse PEM block - var block *pem.Block - if block, _ = pem.Decode(key); block == nil { - return nil, ErrKeyMustBePEMEncoded - } - - // Parse the key - var parsedKey interface{} - if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { - if cert, err := x509.ParseCertificate(block.Bytes); err == nil { - parsedKey = cert.PublicKey - } else { - return nil, err - } - } - - var pkey *ecdsa.PublicKey - var ok bool - if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok { - return nil, ErrNotECPublicKey - } - - return pkey, nil -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/errors.go b/src/vendor/github.com/dgrijalva/jwt-go/errors.go deleted file mode 100644 index 1c93024aad2..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/errors.go +++ /dev/null @@ -1,59 +0,0 @@ -package jwt - -import ( - "errors" -) - -// Error constants -var ( - ErrInvalidKey = errors.New("key is invalid") - ErrInvalidKeyType = errors.New("key is of invalid type") - ErrHashUnavailable = errors.New("the requested hash function is unavailable") -) - -// The errors that might occur when parsing and validating a token -const ( - ValidationErrorMalformed uint32 = 1 << iota // Token is malformed - ValidationErrorUnverifiable // Token could not be verified because of signing problems - ValidationErrorSignatureInvalid // Signature validation failed - - // Standard Claim validation errors - ValidationErrorAudience // AUD validation failed - ValidationErrorExpired // EXP validation failed - ValidationErrorIssuedAt // IAT validation failed - ValidationErrorIssuer // ISS validation failed - ValidationErrorNotValidYet // NBF validation failed - ValidationErrorId // JTI validation failed - ValidationErrorClaimsInvalid // Generic claims validation error -) - -// Helper for constructing a ValidationError with a string error message -func NewValidationError(errorText string, errorFlags uint32) *ValidationError { - return &ValidationError{ - text: errorText, - Errors: errorFlags, - } -} - -// The error from Parse if token is not valid -type ValidationError struct { - Inner error // stores the error returned by external dependencies, i.e.: KeyFunc - Errors uint32 // bitfield. see ValidationError... constants - text string // errors that do not have a valid error just have text -} - -// Validation error is an error type -func (e ValidationError) Error() string { - if e.Inner != nil { - return e.Inner.Error() - } else if e.text != "" { - return e.text - } else { - return "token is invalid" - } -} - -// No errors -func (e *ValidationError) valid() bool { - return e.Errors == 0 -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/hmac.go b/src/vendor/github.com/dgrijalva/jwt-go/hmac.go deleted file mode 100644 index addbe5d4018..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/hmac.go +++ /dev/null @@ -1,95 +0,0 @@ -package jwt - -import ( - "crypto" - "crypto/hmac" - "errors" -) - -// Implements the HMAC-SHA family of signing methods signing methods -// Expects key type of []byte for both signing and validation -type SigningMethodHMAC struct { - Name string - Hash crypto.Hash -} - -// Specific instances for HS256 and company -var ( - SigningMethodHS256 *SigningMethodHMAC - SigningMethodHS384 *SigningMethodHMAC - SigningMethodHS512 *SigningMethodHMAC - ErrSignatureInvalid = errors.New("signature is invalid") -) - -func init() { - // HS256 - SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256} - RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod { - return SigningMethodHS256 - }) - - // HS384 - SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384} - RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod { - return SigningMethodHS384 - }) - - // HS512 - SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512} - RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod { - return SigningMethodHS512 - }) -} - -func (m *SigningMethodHMAC) Alg() string { - return m.Name -} - -// Verify the signature of HSXXX tokens. Returns nil if the signature is valid. -func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error { - // Verify the key is the right type - keyBytes, ok := key.([]byte) - if !ok { - return ErrInvalidKeyType - } - - // Decode signature, for comparison - sig, err := DecodeSegment(signature) - if err != nil { - return err - } - - // Can we use the specified hashing method? - if !m.Hash.Available() { - return ErrHashUnavailable - } - - // This signing method is symmetric, so we validate the signature - // by reproducing the signature from the signing string and key, then - // comparing that against the provided signature. - hasher := hmac.New(m.Hash.New, keyBytes) - hasher.Write([]byte(signingString)) - if !hmac.Equal(sig, hasher.Sum(nil)) { - return ErrSignatureInvalid - } - - // No validation errors. Signature is good. - return nil -} - -// Implements the Sign method from SigningMethod for this signing method. -// Key must be []byte -func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) { - if keyBytes, ok := key.([]byte); ok { - if !m.Hash.Available() { - return "", ErrHashUnavailable - } - - hasher := hmac.New(m.Hash.New, keyBytes) - hasher.Write([]byte(signingString)) - - return EncodeSegment(hasher.Sum(nil)), nil - } - - return "", ErrInvalidKeyType -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/map_claims.go b/src/vendor/github.com/dgrijalva/jwt-go/map_claims.go deleted file mode 100644 index 291213c460d..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/map_claims.go +++ /dev/null @@ -1,94 +0,0 @@ -package jwt - -import ( - "encoding/json" - "errors" - // "fmt" -) - -// Claims type that uses the map[string]interface{} for JSON decoding -// This is the default claims type if you don't supply one -type MapClaims map[string]interface{} - -// Compares the aud claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (m MapClaims) VerifyAudience(cmp string, req bool) bool { - aud, _ := m["aud"].(string) - return verifyAud(aud, cmp, req) -} - -// Compares the exp claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool { - switch exp := m["exp"].(type) { - case float64: - return verifyExp(int64(exp), cmp, req) - case json.Number: - v, _ := exp.Int64() - return verifyExp(v, cmp, req) - } - return req == false -} - -// Compares the iat claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { - switch iat := m["iat"].(type) { - case float64: - return verifyIat(int64(iat), cmp, req) - case json.Number: - v, _ := iat.Int64() - return verifyIat(v, cmp, req) - } - return req == false -} - -// Compares the iss claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { - iss, _ := m["iss"].(string) - return verifyIss(iss, cmp, req) -} - -// Compares the nbf claim against cmp. -// If required is false, this method will return true if the value matches or is unset -func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool { - switch nbf := m["nbf"].(type) { - case float64: - return verifyNbf(int64(nbf), cmp, req) - case json.Number: - v, _ := nbf.Int64() - return verifyNbf(v, cmp, req) - } - return req == false -} - -// Validates time based claims "exp, iat, nbf". -// There is no accounting for clock skew. -// As well, if any of the above claims are not in the token, it will still -// be considered a valid claim. -func (m MapClaims) Valid() error { - vErr := new(ValidationError) - now := TimeFunc().Unix() - - if m.VerifyExpiresAt(now, false) == false { - vErr.Inner = errors.New("Token is expired") - vErr.Errors |= ValidationErrorExpired - } - - if m.VerifyIssuedAt(now, false) == false { - vErr.Inner = errors.New("Token used before issued") - vErr.Errors |= ValidationErrorIssuedAt - } - - if m.VerifyNotBefore(now, false) == false { - vErr.Inner = errors.New("Token is not valid yet") - vErr.Errors |= ValidationErrorNotValidYet - } - - if vErr.valid() { - return nil - } - - return vErr -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/none.go b/src/vendor/github.com/dgrijalva/jwt-go/none.go deleted file mode 100644 index f04d189d067..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/none.go +++ /dev/null @@ -1,52 +0,0 @@ -package jwt - -// Implements the none signing method. This is required by the spec -// but you probably should never use it. -var SigningMethodNone *signingMethodNone - -const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" - -var NoneSignatureTypeDisallowedError error - -type signingMethodNone struct{} -type unsafeNoneMagicConstant string - -func init() { - SigningMethodNone = &signingMethodNone{} - NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid) - - RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod { - return SigningMethodNone - }) -} - -func (m *signingMethodNone) Alg() string { - return "none" -} - -// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key -func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) { - // Key must be UnsafeAllowNoneSignatureType to prevent accidentally - // accepting 'none' signing method - if _, ok := key.(unsafeNoneMagicConstant); !ok { - return NoneSignatureTypeDisallowedError - } - // If signing method is none, signature must be an empty string - if signature != "" { - return NewValidationError( - "'none' signing method with non-empty signature", - ValidationErrorSignatureInvalid, - ) - } - - // Accept 'none' signing method. - return nil -} - -// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key -func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) { - if _, ok := key.(unsafeNoneMagicConstant); ok { - return "", nil - } - return "", NoneSignatureTypeDisallowedError -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/parser.go b/src/vendor/github.com/dgrijalva/jwt-go/parser.go deleted file mode 100644 index d6901d9adb5..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/parser.go +++ /dev/null @@ -1,148 +0,0 @@ -package jwt - -import ( - "bytes" - "encoding/json" - "fmt" - "strings" -) - -type Parser struct { - ValidMethods []string // If populated, only these methods will be considered valid - UseJSONNumber bool // Use JSON Number format in JSON decoder - SkipClaimsValidation bool // Skip claims validation during token parsing -} - -// Parse, validate, and return a token. -// keyFunc will receive the parsed token and should return the key for validating. -// If everything is kosher, err will be nil -func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { - return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc) -} - -func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { - token, parts, err := p.ParseUnverified(tokenString, claims) - if err != nil { - return token, err - } - - // Verify signing method is in the required set - if p.ValidMethods != nil { - var signingMethodValid = false - var alg = token.Method.Alg() - for _, m := range p.ValidMethods { - if m == alg { - signingMethodValid = true - break - } - } - if !signingMethodValid { - // signing method is not in the listed set - return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid) - } - } - - // Lookup key - var key interface{} - if keyFunc == nil { - // keyFunc was not provided. short circuiting validation - return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable) - } - if key, err = keyFunc(token); err != nil { - // keyFunc returned an error - if ve, ok := err.(*ValidationError); ok { - return token, ve - } - return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} - } - - vErr := &ValidationError{} - - // Validate Claims - if !p.SkipClaimsValidation { - if err := token.Claims.Valid(); err != nil { - - // If the Claims Valid returned an error, check if it is a validation error, - // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set - if e, ok := err.(*ValidationError); !ok { - vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} - } else { - vErr = e - } - } - } - - // Perform validation - token.Signature = parts[2] - if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { - vErr.Inner = err - vErr.Errors |= ValidationErrorSignatureInvalid - } - - if vErr.valid() { - token.Valid = true - return token, nil - } - - return token, vErr -} - -// WARNING: Don't use this method unless you know what you're doing -// -// This method parses the token but doesn't validate the signature. It's only -// ever useful in cases where you know the signature is valid (because it has -// been checked previously in the stack) and you want to extract values from -// it. -func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) { - parts = strings.Split(tokenString, ".") - if len(parts) != 3 { - return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) - } - - token = &Token{Raw: tokenString} - - // parse Header - var headerBytes []byte - if headerBytes, err = DecodeSegment(parts[0]); err != nil { - if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { - return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed) - } - return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - if err = json.Unmarshal(headerBytes, &token.Header); err != nil { - return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - - // parse Claims - var claimBytes []byte - token.Claims = claims - - if claimBytes, err = DecodeSegment(parts[1]); err != nil { - return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) - if p.UseJSONNumber { - dec.UseNumber() - } - // JSON Decode. Special case for map type to avoid weird pointer behavior - if c, ok := token.Claims.(MapClaims); ok { - err = dec.Decode(&c) - } else { - err = dec.Decode(&claims) - } - // Handle decode error - if err != nil { - return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - - // Lookup signature method - if method, ok := token.Header["alg"].(string); ok { - if token.Method = GetSigningMethod(method); token.Method == nil { - return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable) - } - } else { - return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable) - } - - return token, parts, nil -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/rsa.go b/src/vendor/github.com/dgrijalva/jwt-go/rsa.go deleted file mode 100644 index e4caf1ca4a1..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/rsa.go +++ /dev/null @@ -1,101 +0,0 @@ -package jwt - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" -) - -// Implements the RSA family of signing methods signing methods -// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation -type SigningMethodRSA struct { - Name string - Hash crypto.Hash -} - -// Specific instances for RS256 and company -var ( - SigningMethodRS256 *SigningMethodRSA - SigningMethodRS384 *SigningMethodRSA - SigningMethodRS512 *SigningMethodRSA -) - -func init() { - // RS256 - SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256} - RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod { - return SigningMethodRS256 - }) - - // RS384 - SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384} - RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod { - return SigningMethodRS384 - }) - - // RS512 - SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512} - RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod { - return SigningMethodRS512 - }) -} - -func (m *SigningMethodRSA) Alg() string { - return m.Name -} - -// Implements the Verify method from SigningMethod -// For this signing method, must be an *rsa.PublicKey structure. -func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error { - var err error - - // Decode the signature - var sig []byte - if sig, err = DecodeSegment(signature); err != nil { - return err - } - - var rsaKey *rsa.PublicKey - var ok bool - - if rsaKey, ok = key.(*rsa.PublicKey); !ok { - return ErrInvalidKeyType - } - - // Create hasher - if !m.Hash.Available() { - return ErrHashUnavailable - } - hasher := m.Hash.New() - hasher.Write([]byte(signingString)) - - // Verify the signature - return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig) -} - -// Implements the Sign method from SigningMethod -// For this signing method, must be an *rsa.PrivateKey structure. -func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) { - var rsaKey *rsa.PrivateKey - var ok bool - - // Validate type of key - if rsaKey, ok = key.(*rsa.PrivateKey); !ok { - return "", ErrInvalidKey - } - - // Create the hasher - if !m.Hash.Available() { - return "", ErrHashUnavailable - } - - hasher := m.Hash.New() - hasher.Write([]byte(signingString)) - - // Sign the string and return the encoded bytes - if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil { - return EncodeSegment(sigBytes), nil - } else { - return "", err - } -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/rsa_pss.go b/src/vendor/github.com/dgrijalva/jwt-go/rsa_pss.go deleted file mode 100644 index 10ee9db8a4e..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/rsa_pss.go +++ /dev/null @@ -1,126 +0,0 @@ -// +build go1.4 - -package jwt - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" -) - -// Implements the RSAPSS family of signing methods signing methods -type SigningMethodRSAPSS struct { - *SigningMethodRSA - Options *rsa.PSSOptions -} - -// Specific instances for RS/PS and company -var ( - SigningMethodPS256 *SigningMethodRSAPSS - SigningMethodPS384 *SigningMethodRSAPSS - SigningMethodPS512 *SigningMethodRSAPSS -) - -func init() { - // PS256 - SigningMethodPS256 = &SigningMethodRSAPSS{ - &SigningMethodRSA{ - Name: "PS256", - Hash: crypto.SHA256, - }, - &rsa.PSSOptions{ - SaltLength: rsa.PSSSaltLengthAuto, - Hash: crypto.SHA256, - }, - } - RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod { - return SigningMethodPS256 - }) - - // PS384 - SigningMethodPS384 = &SigningMethodRSAPSS{ - &SigningMethodRSA{ - Name: "PS384", - Hash: crypto.SHA384, - }, - &rsa.PSSOptions{ - SaltLength: rsa.PSSSaltLengthAuto, - Hash: crypto.SHA384, - }, - } - RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod { - return SigningMethodPS384 - }) - - // PS512 - SigningMethodPS512 = &SigningMethodRSAPSS{ - &SigningMethodRSA{ - Name: "PS512", - Hash: crypto.SHA512, - }, - &rsa.PSSOptions{ - SaltLength: rsa.PSSSaltLengthAuto, - Hash: crypto.SHA512, - }, - } - RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod { - return SigningMethodPS512 - }) -} - -// Implements the Verify method from SigningMethod -// For this verify method, key must be an rsa.PublicKey struct -func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error { - var err error - - // Decode the signature - var sig []byte - if sig, err = DecodeSegment(signature); err != nil { - return err - } - - var rsaKey *rsa.PublicKey - switch k := key.(type) { - case *rsa.PublicKey: - rsaKey = k - default: - return ErrInvalidKey - } - - // Create hasher - if !m.Hash.Available() { - return ErrHashUnavailable - } - hasher := m.Hash.New() - hasher.Write([]byte(signingString)) - - return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options) -} - -// Implements the Sign method from SigningMethod -// For this signing method, key must be an rsa.PrivateKey struct -func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) { - var rsaKey *rsa.PrivateKey - - switch k := key.(type) { - case *rsa.PrivateKey: - rsaKey = k - default: - return "", ErrInvalidKeyType - } - - // Create the hasher - if !m.Hash.Available() { - return "", ErrHashUnavailable - } - - hasher := m.Hash.New() - hasher.Write([]byte(signingString)) - - // Sign the string and return the encoded bytes - if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil { - return EncodeSegment(sigBytes), nil - } else { - return "", err - } -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go b/src/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go deleted file mode 100644 index a5ababf956c..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go +++ /dev/null @@ -1,101 +0,0 @@ -package jwt - -import ( - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "errors" -) - -var ( - ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") - ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key") - ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key") -) - -// Parse PEM encoded PKCS1 or PKCS8 private key -func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { - var err error - - // Parse PEM block - var block *pem.Block - if block, _ = pem.Decode(key); block == nil { - return nil, ErrKeyMustBePEMEncoded - } - - var parsedKey interface{} - if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { - if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { - return nil, err - } - } - - var pkey *rsa.PrivateKey - var ok bool - if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { - return nil, ErrNotRSAPrivateKey - } - - return pkey, nil -} - -// Parse PEM encoded PKCS1 or PKCS8 private key protected with password -func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) { - var err error - - // Parse PEM block - var block *pem.Block - if block, _ = pem.Decode(key); block == nil { - return nil, ErrKeyMustBePEMEncoded - } - - var parsedKey interface{} - - var blockDecrypted []byte - if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil { - return nil, err - } - - if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil { - if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil { - return nil, err - } - } - - var pkey *rsa.PrivateKey - var ok bool - if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { - return nil, ErrNotRSAPrivateKey - } - - return pkey, nil -} - -// Parse PEM encoded PKCS1 or PKCS8 public key -func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { - var err error - - // Parse PEM block - var block *pem.Block - if block, _ = pem.Decode(key); block == nil { - return nil, ErrKeyMustBePEMEncoded - } - - // Parse the key - var parsedKey interface{} - if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { - if cert, err := x509.ParseCertificate(block.Bytes); err == nil { - parsedKey = cert.PublicKey - } else { - return nil, err - } - } - - var pkey *rsa.PublicKey - var ok bool - if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { - return nil, ErrNotRSAPublicKey - } - - return pkey, nil -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/signing_method.go b/src/vendor/github.com/dgrijalva/jwt-go/signing_method.go deleted file mode 100644 index ed1f212b21e..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/signing_method.go +++ /dev/null @@ -1,35 +0,0 @@ -package jwt - -import ( - "sync" -) - -var signingMethods = map[string]func() SigningMethod{} -var signingMethodLock = new(sync.RWMutex) - -// Implement SigningMethod to add new methods for signing or verifying tokens. -type SigningMethod interface { - Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid - Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error - Alg() string // returns the alg identifier for this method (example: 'HS256') -} - -// Register the "alg" name and a factory function for signing method. -// This is typically done during init() in the method's implementation -func RegisterSigningMethod(alg string, f func() SigningMethod) { - signingMethodLock.Lock() - defer signingMethodLock.Unlock() - - signingMethods[alg] = f -} - -// Get a signing method from an "alg" string -func GetSigningMethod(alg string) (method SigningMethod) { - signingMethodLock.RLock() - defer signingMethodLock.RUnlock() - - if methodF, ok := signingMethods[alg]; ok { - method = methodF() - } - return -} diff --git a/src/vendor/github.com/dgrijalva/jwt-go/token.go b/src/vendor/github.com/dgrijalva/jwt-go/token.go deleted file mode 100644 index d637e0867c6..00000000000 --- a/src/vendor/github.com/dgrijalva/jwt-go/token.go +++ /dev/null @@ -1,108 +0,0 @@ -package jwt - -import ( - "encoding/base64" - "encoding/json" - "strings" - "time" -) - -// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time). -// You can override it to use another time value. This is useful for testing or if your -// server uses a different time zone than your tokens. -var TimeFunc = time.Now - -// Parse methods use this callback function to supply -// the key for verification. The function receives the parsed, -// but unverified Token. This allows you to use properties in the -// Header of the token (such as `kid`) to identify which key to use. -type Keyfunc func(*Token) (interface{}, error) - -// A JWT Token. Different fields will be used depending on whether you're -// creating or parsing/verifying a token. -type Token struct { - Raw string // The raw token. Populated when you Parse a token - Method SigningMethod // The signing method used or to be used - Header map[string]interface{} // The first segment of the token - Claims Claims // The second segment of the token - Signature string // The third segment of the token. Populated when you Parse a token - Valid bool // Is the token valid? Populated when you Parse/Verify a token -} - -// Create a new Token. Takes a signing method -func New(method SigningMethod) *Token { - return NewWithClaims(method, MapClaims{}) -} - -func NewWithClaims(method SigningMethod, claims Claims) *Token { - return &Token{ - Header: map[string]interface{}{ - "typ": "JWT", - "alg": method.Alg(), - }, - Claims: claims, - Method: method, - } -} - -// Get the complete, signed token -func (t *Token) SignedString(key interface{}) (string, error) { - var sig, sstr string - var err error - if sstr, err = t.SigningString(); err != nil { - return "", err - } - if sig, err = t.Method.Sign(sstr, key); err != nil { - return "", err - } - return strings.Join([]string{sstr, sig}, "."), nil -} - -// Generate the signing string. This is the -// most expensive part of the whole deal. Unless you -// need this for something special, just go straight for -// the SignedString. -func (t *Token) SigningString() (string, error) { - var err error - parts := make([]string, 2) - for i, _ := range parts { - var jsonValue []byte - if i == 0 { - if jsonValue, err = json.Marshal(t.Header); err != nil { - return "", err - } - } else { - if jsonValue, err = json.Marshal(t.Claims); err != nil { - return "", err - } - } - - parts[i] = EncodeSegment(jsonValue) - } - return strings.Join(parts, "."), nil -} - -// Parse, validate, and return a token. -// keyFunc will receive the parsed token and should return the key for validating. -// If everything is kosher, err will be nil -func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { - return new(Parser).Parse(tokenString, keyFunc) -} - -func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { - return new(Parser).ParseWithClaims(tokenString, claims, keyFunc) -} - -// Encode JWT specific base64url encoding with padding stripped -func EncodeSegment(seg []byte) string { - return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") -} - -// Decode JWT specific base64url encoding with padding stripped -func DecodeSegment(seg string) ([]byte, error) { - if l := len(seg) % 4; l > 0 { - seg += strings.Repeat("=", 4-l) - } - - return base64.URLEncoding.DecodeString(seg) -} diff --git a/src/vendor/github.com/jpillora/backoff/LICENSE b/src/vendor/github.com/jpillora/backoff/LICENSE new file mode 100644 index 00000000000..1cc708081b3 --- /dev/null +++ b/src/vendor/github.com/jpillora/backoff/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Jaime Pillora + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/vendor/github.com/jpillora/backoff/README.md b/src/vendor/github.com/jpillora/backoff/README.md new file mode 100644 index 00000000000..ee4d6230afe --- /dev/null +++ b/src/vendor/github.com/jpillora/backoff/README.md @@ -0,0 +1,119 @@ +# Backoff + +A simple exponential backoff counter in Go (Golang) + +[![GoDoc](https://godoc.org/github.com/jpillora/backoff?status.svg)](https://godoc.org/github.com/jpillora/backoff) [![Circle CI](https://circleci.com/gh/jpillora/backoff.svg?style=shield)](https://circleci.com/gh/jpillora/backoff) + +### Install + +``` +$ go get -v github.com/jpillora/backoff +``` + +### Usage + +Backoff is a `time.Duration` counter. It starts at `Min`. After every call to `Duration()` it is multiplied by `Factor`. It is capped at `Max`. It returns to `Min` on every call to `Reset()`. `Jitter` adds randomness ([see below](#example-using-jitter)). Used in conjunction with the `time` package. + +--- + +#### Simple example + +``` go + +b := &backoff.Backoff{ + //These are the defaults + Min: 100 * time.Millisecond, + Max: 10 * time.Second, + Factor: 2, + Jitter: false, +} + +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) + +fmt.Printf("Reset!\n") +b.Reset() + +fmt.Printf("%s\n", b.Duration()) +``` + +``` +100ms +200ms +400ms +Reset! +100ms +``` + +--- + +#### Example using `net` package + +``` go +b := &backoff.Backoff{ + Max: 5 * time.Minute, +} + +for { + conn, err := net.Dial("tcp", "example.com:5309") + if err != nil { + d := b.Duration() + fmt.Printf("%s, reconnecting in %s", err, d) + time.Sleep(d) + continue + } + //connected + b.Reset() + conn.Write([]byte("hello world!")) + // ... Read ... Write ... etc + conn.Close() + //disconnected +} + +``` + +--- + +#### Example using `Jitter` + +Enabling `Jitter` adds some randomization to the backoff durations. [See Amazon's writeup of performance gains using jitter](http://www.awsarchitectureblog.com/2015/03/backoff.html). Seeding is not necessary but doing so gives repeatable results. + +```go +import "math/rand" + +b := &backoff.Backoff{ + Jitter: true, +} + +rand.Seed(42) + +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) + +fmt.Printf("Reset!\n") +b.Reset() + +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) +``` + +``` +100ms +106.600049ms +281.228155ms +Reset! +100ms +104.381845ms +214.957989ms +``` + +#### Documentation + +https://godoc.org/github.com/jpillora/backoff + +#### Credits + +Forked from [some JavaScript](https://github.com/segmentio/backo) written by [@tj](https://github.com/tj) diff --git a/src/vendor/github.com/jpillora/backoff/backoff.go b/src/vendor/github.com/jpillora/backoff/backoff.go new file mode 100644 index 00000000000..d113e68906b --- /dev/null +++ b/src/vendor/github.com/jpillora/backoff/backoff.go @@ -0,0 +1,100 @@ +// Package backoff provides an exponential-backoff implementation. +package backoff + +import ( + "math" + "math/rand" + "sync/atomic" + "time" +) + +// Backoff is a time.Duration counter, starting at Min. After every call to +// the Duration method the current timing is multiplied by Factor, but it +// never exceeds Max. +// +// Backoff is not generally concurrent-safe, but the ForAttempt method can +// be used concurrently. +type Backoff struct { + attempt uint64 + // Factor is the multiplying factor for each increment step + Factor float64 + // Jitter eases contention by randomizing backoff steps + Jitter bool + // Min and Max are the minimum and maximum values of the counter + Min, Max time.Duration +} + +// Duration returns the duration for the current attempt before incrementing +// the attempt counter. See ForAttempt. +func (b *Backoff) Duration() time.Duration { + d := b.ForAttempt(float64(atomic.AddUint64(&b.attempt, 1) - 1)) + return d +} + +const maxInt64 = float64(math.MaxInt64 - 512) + +// ForAttempt returns the duration for a specific attempt. This is useful if +// you have a large number of independent Backoffs, but don't want use +// unnecessary memory storing the Backoff parameters per Backoff. The first +// attempt should be 0. +// +// ForAttempt is concurrent-safe. +func (b *Backoff) ForAttempt(attempt float64) time.Duration { + // Zero-values are nonsensical, so we use + // them to apply defaults + min := b.Min + if min <= 0 { + min = 100 * time.Millisecond + } + max := b.Max + if max <= 0 { + max = 10 * time.Second + } + if min >= max { + // short-circuit + return max + } + factor := b.Factor + if factor <= 0 { + factor = 2 + } + //calculate this duration + minf := float64(min) + durf := minf * math.Pow(factor, attempt) + if b.Jitter { + durf = rand.Float64()*(durf-minf) + minf + } + //ensure float64 wont overflow int64 + if durf > maxInt64 { + return max + } + dur := time.Duration(durf) + //keep within bounds + if dur < min { + return min + } + if dur > max { + return max + } + return dur +} + +// Reset restarts the current attempt counter at zero. +func (b *Backoff) Reset() { + atomic.StoreUint64(&b.attempt, 0) +} + +// Attempt returns the current attempt counter value. +func (b *Backoff) Attempt() float64 { + return float64(atomic.LoadUint64(&b.attempt)) +} + +// Copy returns a backoff with equals constraints as the original +func (b *Backoff) Copy() *Backoff { + return &Backoff{ + Factor: b.Factor, + Jitter: b.Jitter, + Min: b.Min, + Max: b.Max, + } +} diff --git a/src/vendor/github.com/jpillora/backoff/go.mod b/src/vendor/github.com/jpillora/backoff/go.mod new file mode 100644 index 00000000000..7c41bc6f583 --- /dev/null +++ b/src/vendor/github.com/jpillora/backoff/go.mod @@ -0,0 +1,3 @@ +module github.com/jpillora/backoff + +go 1.13 diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index b217e3a947d..58ea804de0e 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -226,8 +226,6 @@ github.com/denverdino/aliyungo/util # github.com/dghubble/sling v1.1.0 ## explicit github.com/dghubble/sling -# github.com/dgrijalva/jwt-go v3.2.0+incompatible -github.com/dgrijalva/jwt-go # github.com/docker/cli v20.10.5+incompatible github.com/docker/cli/cli/config github.com/docker/cli/cli/config/configfile @@ -491,6 +489,9 @@ github.com/inconshreveable/mousetrap ## explicit # github.com/jmespath/go-jmespath v0.3.0 github.com/jmespath/go-jmespath +# github.com/jpillora/backoff v1.0.0 +## explicit +github.com/jpillora/backoff # github.com/json-iterator/go v1.1.10 github.com/json-iterator/go # github.com/lib/pq v1.10.0 From e0c4ebd84d90fa8f50080aa94aec4a427da89b3e Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Mon, 30 Aug 2021 13:40:33 +0800 Subject: [PATCH 011/135] fix gc delete manifest log issue (#15495) It needs to use the logger to print error log when timeout, otherwise, it will be dropped. Signed-off-by: Wang Yan --- src/jobservice/job/impl/gc/garbage_collection.go | 12 ++++++------ src/jobservice/job/impl/gc/util.go | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/jobservice/job/impl/gc/garbage_collection.go b/src/jobservice/job/impl/gc/garbage_collection.go index 357b2114389..8dc1dd0cc90 100644 --- a/src/jobservice/job/impl/gc/garbage_collection.go +++ b/src/jobservice/job/impl/gc/garbage_collection.go @@ -260,7 +260,7 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error { // Harbor cannot know the existing tags in the backend from its database, so let the v2 DELETE manifest to remove all of them. gc.logger.Infof("delete the manifest with registry v2 API: %s, %s, %s", art.RepositoryName, blob.ContentType, blob.Digest) - if err := v2DeleteManifest(art.RepositoryName, blob.Digest); err != nil { + if err := v2DeleteManifest(gc.logger, art.RepositoryName, blob.Digest); err != nil { gc.logger.Errorf("failed to delete manifest with v2 API, %s, %s, %v", art.RepositoryName, blob.Digest, err) if err := ignoreNotFound(func() error { return gc.markDeleteFailed(ctx, blob) @@ -276,7 +276,7 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error { return gc.registryCtlClient.DeleteManifest(art.RepositoryName, blob.Digest) }) }, retry.Callback(func(err error, sleep time.Duration) { - gc.logger.Infof("failed to exec DeleteManifest retry after %s : %v", sleep, err) + gc.logger.Infof("failed to exec DeleteManifest, error: %v, will retry again after: %s", err, sleep) })); err != nil { if err := ignoreNotFound(func() error { return gc.markDeleteFailed(ctx, blob) @@ -304,7 +304,7 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error { return gc.registryCtlClient.DeleteBlob(blob.Digest) }) }, retry.Callback(func(err error, sleep time.Duration) { - gc.logger.Infof("failed to exec DeleteBlob retry after %s : %v", sleep, err) + gc.logger.Infof("failed to exec DeleteBlob, error: %v, will retry again after: %s", err, sleep) })); err != nil { if err := ignoreNotFound(func() error { return gc.markDeleteFailed(ctx, blob) @@ -461,19 +461,19 @@ func (gc *GarbageCollector) markOrSweepUntaggedBlobs(ctx job.Context) []*blob_mo } blobs, err := gc.blobMgr.List(ctx.SystemContext(), q) if err != nil { - gc.logger.Errorf("failed to get blobs of project, %v", err) + gc.logger.Errorf("failed to get blobs of project: %d, %v", p.ProjectID, err) break } if gc.dryRun { unassociated, err := gc.blobMgr.FindBlobsShouldUnassociatedWithProject(ctx.SystemContext(), p.ProjectID, blobs) if err != nil { - gc.logger.Errorf("failed to find untagged blobs of project, %v", err) + gc.logger.Errorf("failed to find untagged blobs of project: %d, %v", p.ProjectID, err) break } untaggedBlobs = append(untaggedBlobs, unassociated...) } else { if err := gc.blobMgr.CleanupAssociationsForProject(ctx.SystemContext(), p.ProjectID, blobs); err != nil { - gc.logger.Errorf("failed to clean untagged blobs of project, %v", err) + gc.logger.Errorf("failed to clean untagged blobs of project: %d, %v", p.ProjectID, err) break } } diff --git a/src/jobservice/job/impl/gc/util.go b/src/jobservice/job/impl/gc/util.go index 3e2da0b428d..b8c4f5789b3 100644 --- a/src/jobservice/job/impl/gc/util.go +++ b/src/jobservice/job/impl/gc/util.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/goharbor/harbor/src/jobservice/logger" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/retry" "github.com/goharbor/harbor/src/pkg/registry" @@ -43,7 +44,7 @@ func delKeys(con redis.Conn, pattern string) error { } // v2DeleteManifest calls the registry API to remove manifest -func v2DeleteManifest(repository, digest string) error { +func v2DeleteManifest(logger logger.Interface, repository, digest string) error { exist, _, err := registry.Cli.ManifestExist(repository, digest) if err != nil { return err @@ -56,7 +57,7 @@ func v2DeleteManifest(repository, digest string) error { return retry.Retry(func() error { return registry.Cli.DeleteManifest(repository, digest) }, retry.Callback(func(err error, sleep time.Duration) { - fmt.Printf("failed to exec DeleteManifest retry after %s : %v\n", sleep, err) + logger.Infof("failed to exec v2DeleteManifest, error: %v, will retry again after: %s", err, sleep) })) } From 9799598f338df81073842070e9a02ccf56400c85 Mon Sep 17 00:00:00 2001 From: Julio H Morimoto Date: Tue, 31 Aug 2021 04:39:36 -0300 Subject: [PATCH 012/135] Fixes https://github.com/goharbor/harbor/issues/15454. (#15455) Signed-off-by: Julio Morimoto --- src/portal/src/i18n/lang/pt-br-lang.json | 1760 +++++++++++----------- 1 file changed, 880 insertions(+), 880 deletions(-) diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 4060daee11e..12d865f0e90 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -3,40 +3,40 @@ "VMW_HARBOR": "Harbor", "HARBOR": "Harbor", "VIC": "vSphere Integrated Containers", - "MGMT": "Gerência", + "MGMT": "Administração", "REG": "Registro", "HARBOR_SWAGGER": "Harbor Swagger", - "THEME_DARK_TEXT": "DARK", - "THEME_LIGHT_TEXT": "LIGHT" + "THEME_DARK_TEXT": "ESCURO", + "THEME_LIGHT_TEXT": "CLARO" }, "SIGN_IN": { - "REMEMBER": "Lembrar-se de mim", + "REMEMBER": "Lembrar de mim", "INVALID_MSG": "Usuário ou senha inválidos", "FORGOT_PWD": "Esqueci a senha", - "HEADER_LINK": "Logar-se", - "CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.", - "OR": "OR", - "VIA_LOCAL_DB": "LOGIN VIA LOCAL DB" + "HEADER_LINK": "Login", + "CORE_SERVICE_NOT_AVAILABLE": "Serviço 'core' indisponível.", + "OR": "OU", + "VIA_LOCAL_DB": "LOGIN COM BANCO DE DADOS LOCAL" }, "SIGN_UP": { - "TITLE": "Registrar-se" + "TITLE": "Cadastro de conta" }, "BUTTON": { - "STOP": "STOP", + "STOP": "PARAR", "CANCEL": "CANCELAR", "OK": "OK", - "DELETE": "DELETAR", - "LOG_IN": "LOG IN", - "LOG_IN_OIDC": "Login via OIDC provedor", - "SIGN_UP_LINK": "Registre-se para uma conta", - "SIGN_UP": "REGISTRE-Se", + "DELETE": "REMOVER", + "LOG_IN": "ENTRAR", + "LOG_IN_OIDC": "Entrar com provedor OIDC", + "SIGN_UP_LINK": "Criar uma nova conta", + "SIGN_UP": "CADASTRO", "CONFIRM": "CONFIRMAR", "SEND": "ENVIAR", "SAVE": "SALVAR", "TEST_MAIL": "TESTAR SERVIDOR DE EMAIL", "CLOSE": "FECHAR", - "TEST_LDAP": "TESTAR SERVIDOR DE LDAP", - "TEST_OIDC": "TEST OIDC SERVER", + "TEST_LDAP": "TESTAR SERVIDOR LDAP", + "TEST_OIDC": "TESTAR PROVEDOR OIDC", "MORE_INFO": "Mais informações...", "YES": "SIM", "NO": "NÃO", @@ -49,98 +49,99 @@ "BROWSE": "Navegar", "UPLOAD": "Upload", "NO_FILE": "Nenhum arquivo selecionado", - "ADD": "ADD", - "RUN": "RUN", - "CONTINUE": "CONTINUE", - "ENABLE": "ENABLE", - "DISABLE": "DISABLE" + "ADD": "ADICIONAR", + "RUN": "EXECUTAR", + "CONTINUE": "CONTINUAR", + "ENABLE": "HABILITAR", + "DISABLE": "DESABILITAR" }, "BATCH": { "DELETED_SUCCESS": "Removido com sucesso", - "DELETED_FAILURE": "Deleted failed or partly failed", + "DELETED_FAILURE": "Falha na remoção", "SWITCH_SUCCESS": "Alterado com sucesso", - "SWITCH_FAILURE": "Falha ao alterar", - "REPLICATE_SUCCESS": "Iniciado com sucesso", - "REPLICATE_FAILURE": "Falha ao iniciar", - "STOP_SUCCESS": "Stop successfully", - "STOP_FAILURE": "Stop execution failed", - "TIME_OUT": "Gateway time-out" + "SWITCH_FAILURE": "Falha na alteração", + "REPLICATE_SUCCESS": "Replicação iniciada", + "REPLICATE_FAILURE": "Falha na replicação", + "STOP_SUCCESS": "Interrompido", + "STOP_FAILURE": "Não foi possível interromper", + "TIME_OUT": "Tempo esgotado (timeout)" }, "TOOLTIP": { - "NAME_FILTER": "Filter the name of the resource. Leave empty or use '**' to match all. 'library/**' only matches resources under 'library'. For more patterns, please refer to the user guide.", - "TAG_FILTER": "Filter the tag/version part of the resources. Leave empty or use '**' to match all. '1.0*' only matches the tags that starts with '1.0'. For more patterns, please refer to the user guide.", - "LABEL_FILTER": "Filter the resources according to labels.", - "RESOURCE_FILTER": "Filter the type of resources.", - "PUSH_BASED": "Push the resources from the local Harbor to the remote registry.", - "PULL_BASED": "Pull the resources from the remote registry to the local Harbor.", - "DESTINATION_NAMESPACE": "Specify the destination namespace. If empty, the resources will be put under the same namespace as the source.", - "OVERRIDE": "Specify whether to override the resources at the destination if a resource with the same name exists.", - "EMAIL": "Email deve ser um endereço de email válido como nome@exemplo.com.", - "USER_NAME": "Não pode conter caracteres especiais e o tamanho máximo deve ser de 255 caracteres.", - "FULL_NAME": "Tamanho máximo deve ser de 20 caracteres.", - "COMMENT": "Tamanho do comentário deve ser menor que 30 caracteres.", + "NAME_FILTER": "Filtrar por nome de recurso. Deixe vazio ou use '**' para ver todos. A expressão 'library/**' seleciona recursos dentro de 'library'. Para mais detalhes, confira a documentação.", + "TAG_FILTER": "Filtrar por tag de cada recurso. Deixe vazio ou use '**' para ver todas. A expressão '1.0*' seleciona todas as tags que começam com 1.0. Para mais detalhes, confira a documentação.", + "LABEL_FILTER": "Filtrar por marcadores.", + "RESOURCE_FILTER": "Filtrar por tipo de recurso.", + "PUSH_BASED": "Enviar recursos locais do Harbor para o repositório remoto.", + "PULL_BASED": "Trazer recursos do repositório remoto para o Harbor local.", + "DESTINATION_NAMESPACE": "Especificar o namespace de destino. Se vazio, os recursos serão colocados no mesmo namespace que a fonte.", + "OVERRIDE": "Sobrescrever recursos no destino se já existir com o mesmo nome.", + "EMAIL": "Deve ser um endereço de e-mail válido como nome@exemplo.com.", + "USER_NAME": "Não pode conter caracteres especiais. Tamanho máximo de 255 caracteres.", + "FULL_NAME": "Tamanho máximo de 20 caracteres.", + "COMMENT": "Comentários devem ter menos de 30 caracteres.", "CURRENT_PWD": "A senha atual é obrigatória.", - "PASSWORD": "Senha deve conter entre 8 e 20 caracteres com ao menos 1 maiuscula, 1 minuscula e 1 número.", + "PASSWORD": "Senha deve ter entre 8 e 20 caracteres. Precisa de pelo menos 1 letra maiuscula, 1 letra minuscula e 1 número.", "CONFIRM_PWD": "As senhas não são iguais.", "SIGN_IN_USERNAME": "Nome de usuário é obrigatório.", "SIGN_IN_PWD": "Senha é obrigatória.", - "SIGN_UP_MAIL": "Email é apenas utilizado para redefinir a senha.", - "SIGN_UP_REAL_NAME": "Primeiro e último nome", - "ITEM_REQUIRED": "Campo é obrigatório.", - "SCOPE_REQUIRED": "Field is required and should be in scope format.", - "NUMBER_REQUIRED": "Campo é obrigatório e deve ser numerico.", - "PORT_REQUIRED": "Campo é obrigatório e deve ser um número de porta válido.", - "CRON_REQUIRED": "O campo é obrigatório e deve estar no formato cron.", - "EMAIL_EXISTING": "Email já existe.", + "SIGN_UP_MAIL": "E-mail é usado apenas para redefinir a senha.", + "SIGN_UP_REAL_NAME": "Nome completo", + "ITEM_REQUIRED": "Campo obrigatório.", + "SCOPE_REQUIRED": "Campo obrigatório. Deve estar no formato apropriado (scope).", + "NUMBER_REQUIRED": "Campo obrigatório. Deve ser numerico.", + "PORT_REQUIRED": "Campo obrigatório. Deve ser um número de porta válido.", + "CRON_REQUIRED": "O campo obrigatório. Deve estar no formato cron.", + "EMAIL_EXISTING": "E-mail já existe.", "USER_EXISTING": "Nome de usuário já está em uso.", "RULE_USER_EXISTING": "Nome já em uso.", "EMPTY": "Nome é obrigatório", - "ENDPOINT_FORMAT": "Avaliação deve começar por HTTP:// Ou HTTPS://.", - "OIDC_ENDPOINT_FORMAT": "Avaliação deve começar por HTTPS://.", - "OIDC_NAME": "O Nome do prestador de oidc.", - "OIDC_ENDPOINT": "A URL de um servidor oidc denúncia.", - "OIDC_SCOPE": "O âmbito de aplicação enviada Ao servidor oidc Durante a autenticação.TEM que conter 'openid' e 'offline_access'.Se você está usando o Google, por favor remova 'offline_access' desse Campo.", - "OIDC_VERIFYCERT": "Desmarque esta opção se o SEU servidor está hospedado oidc via self - signed certificate.", - "OIDC_GROUP_CLAIM": "The name of Claim in the ID token whose value is the list of group names.", - "OIDC_GROUP_CLAIM_WARNING": "It can only contain letters, numbers, underscores, and the input length is no more than 256 characters.", - "OIDC_AUTOONBOARD": "Skip the onboarding screen, so user cannot change its username. Username is provided from ID Token", - "OIDC_USER_CLAIM": "The name of the claim in the ID Token where the username is retrieved from. If not specified, it will default to 'name'", - "NEW_SECRET": "The secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number." + "NONEMPTY": "Não pode ser vazio", + "ENDPOINT_FORMAT": "Endereço deve começar com HTTP:// ou HTTPS://.", + "OIDC_ENDPOINT_FORMAT": "Endereço deve começar com HTTPS://.", + "OIDC_NAME": "Nome do provedor OIDC.", + "OIDC_ENDPOINT": "Endereço do provedor. Deve ser compatível com o protocolo OIDC", + "OIDC_SCOPE": "O âmbito (scope) de aplicação enviada ao provedor OIDC durante a autenticação. Deve incluir 'openid' e 'offline_access'. Se estiver usando Google, remova o valor 'offline_access'.", + "OIDC_VERIFYCERT": "Desmarque para ignorar certificados inválidos ou auto-assinados no provedor ODIC.", + "OIDC_GROUP_CLAIM": "Nome da propriedade (claim) cujo valor representa a lista de grupos do usuário.", + "OIDC_GROUP_CLAIM_WARNING": "Deve conter apenas letras, números e traço-baixo (underscores). Tamanho máximo de 256 caracteres.", + "OIDC_AUTOONBOARD": "Pular tela de alteração durante o cadastro automático. Informações, como nome e e-mail, virão do provedor externo.", + "OIDC_USER_CLAIM": "Nome da propriedade (claim) cujo valor representa o nome de usuário (login). Se não informado, 'name' será usado.", + "NEW_SECRET": "Deve ter mais de 8 caracteres e pelo menos 1 letra maiúscula, 1 minúscula e 1 número." }, "PLACEHOLDER": { - "CURRENT_PWD": "Insira a senha atual", - "NEW_PWD": "Insira a nova senha", + "CURRENT_PWD": "Informe a senha atual", + "NEW_PWD": "Informe a nova senha", "CONFIRM_PWD": "Confirme a nova senha", - "USER_NAME": "Insira o nome de usuário", - "MAIL": "Insira o endereço de email", - "FULL_NAME": "Insira o nome completo", + "USER_NAME": "Informe o nome de usuário", + "MAIL": "Informe o endereço de email", + "FULL_NAME": "Informe o nome completo", "SIGN_IN_NAME": "Nome de usuário", "SIGN_IN_PWD": "Senha" }, "PROFILE": { "TITLE": "Perfil do usuário", - "USER_NAME": "Nome do usuário", - "EMAIL": "Email", - "FULL_NAME": "Primeiro e ultimo nome", + "USER_NAME": "Nome de usuário", + "EMAIL": "E-mail", + "FULL_NAME": "Nome completo", "COMMENT": "Comentários", "PASSWORD": "Senha", - "SAVE_SUCCESS": "Perfil do usuário salvo com sucesso.", - "ADMIN_RENAME_BUTTON": "Alterar nome de usuário", - "ADMIN_RENAME_TIP": "Selecione o botão para alterar o nome de usuário para \"admin@harbor.local\". Essa operação não pode ser desfeita.", + "SAVE_SUCCESS": "Perfil de usuário salvo com sucesso.", + "ADMIN_RENAME_BUTTON": "Redefinir", + "ADMIN_RENAME_TIP": "Altera o nome de usuário para \"admin@harbor.local\". Essa operação não pode ser desfeita!", "RENAME_SUCCESS": "Renomeado com sucesso!", - "RENAME_CONFIRM_INFO": "Atenção, alterar o nome para admin@harbor.local não pode ser desfeito.", + "RENAME_CONFIRM_INFO": "Atenção do nome para admin@harbor.local não poderá ser desfeita.", "CLI_PASSWORD": "Segredo CLI", - "CLI_PASSWORD_TIP": "Você pode usar este segredo como senha ao usar docker / helm cli para acessar o Harbor.", - "COPY_SUCCESS": "SUCESSO de cópia", + "CLI_PASSWORD_TIP": "Você pode usar este segredo como senha ao usar docker/helm cli para acessar o Harbor.", + "COPY_SUCCESS": "Cópia feita com sucesso", "COPY_ERROR": "Cópia falhou", - "ADMIN_CLI_SECRET_BUTTON": "GENERATE SECRET", - "ADMIN_CLI_SECRET_RESET_BUTTON": "Upload Your Own Secret", - "NEW_SECRET": "Secret", - "CONFIRM_SECRET": "Re-enter Secret", - "GENERATE_SUCCESS": "Cli secret setting is successful", - "GENERATE_ERROR": "Cli secret setting is failed", - "CONFIRM_TITLE_CLI_GENERATE": "Are you sure you can regenerate secret?", - "CONFIRM_BODY_CLI_GENERATE": "If you regenerate cli secret, the old cli secret will be discarded" + "ADMIN_CLI_SECRET_BUTTON": "REDEFINIR SEGREDO", + "ADMIN_CLI_SECRET_RESET_BUTTON": "Enviar segredo", + "NEW_SECRET": "Segredo", + "CONFIRM_SECRET": "Confirme o segredo", + "GENERATE_SUCCESS": "Segredo redefinido com sucesso", + "GENERATE_ERROR": "Não foi possível redefinir o segredo", + "CONFIRM_TITLE_CLI_GENERATE": "Gostaria de redefinir o segredo?", + "CONFIRM_BODY_CLI_GENERATE": "Ao fazer isso, o segredo atual não poderá ser recuperado" }, "CHANGE_PWD": { "TITLE": "Alterar Senha", @@ -148,17 +149,17 @@ "NEW_PWD": "Nova senha", "CONFIRM_PWD": "Confirmar senha", "SAVE_SUCCESS": "Senha do usuário alterada com sucesso.", - "PASS_TIPS": "8-128 caracteres com 1 maiuscula, 1 minuscula e 1 número" + "PASS_TIPS": "Senhas devem ter entre 8 e 128 caracteres. Pecisam de pelo menos 1 letra maiuscula, 1 letra minuscula e 1 número" }, "ACCOUNT_SETTINGS": { "PROFILE": "Perfil do usuário", "CHANGE_PWD": "Alterar senha", - "ABOUT": "Sobre", + "ABOUT": "Informativo", "LOGOUT": "Sair" }, "GLOBAL_SEARCH": { - "PLACEHOLDER": "Buscar {{param}}...", - "PLACEHOLDER_VIC": "Buscar registro..." + "PLACEHOLDER": "Busca {{param}}...", + "PLACEHOLDER_VIC": "Busca de registro..." }, "SIDE_NAV": { "DASHBOARD": "Painel de controle", @@ -167,21 +168,21 @@ "NAME": "Administração", "USER": "Usuários", "GROUP": "Grupos", - "REGISTRY": "Registros", + "REGISTRY": "Registries", "REPLICATION": "Replicações", "CONFIG": "Configuração", - "VULNERABILITY": "Vulnerability", - "GARBAGE_COLLECTION": "Garbage Collection", - "INTERROGATION_SERVICES": "Interrogation Services" + "VULNERABILITY": "Vulnerabilidade", + "GARBAGE_COLLECTION": "Limpeza (GC)", + "INTERROGATION_SERVICES": "Serviços de Diagnóstico" }, "LOGS": "Logs", - "TASKS": "Tasks", - "API_EXPLORER": "Api Explorer", + "TASKS": "Tarefas", + "API_EXPLORER": "Explorador da API", "HARBOR_API_MANAGEMENT": "Harbor API V2.0", "HELM_API_MANAGEMENT": "Harbor API", "DISTRIBUTIONS": { - "NAME": "Distributions", - "INSTANCES": "Instances" + "NAME": "Distribuições", + "INSTANCES": "Instâncias" } }, "USER": { @@ -192,8 +193,8 @@ "FILTER_PLACEHOLDER": "Filtrar usuários", "COLUMN_NAME": "Nome", "COLUMN_ADMIN": "Administrador", - "COLUMN_EMAIL": "Email", - "COLUMN_REG_NAME": "Hora do registro", + "COLUMN_EMAIL": "E-mail", + "COLUMN_REG_NAME": "Data de cadastro", "IS_ADMIN": "Sim", "IS_NOT_ADMIN": "Não", "ADD_USER_TITLE": "Novo Usuário", @@ -205,8 +206,8 @@ "OF": "de", "RESET_OK": "Senha do usuário redefinida com sucesso", "EXISTING_PASSWORD": "A nova senha não deve ser igual à antiga", - "UNKNOWN": "Unknown", - "UNKNOWN_TIP": "Please verify whether the user has admin status through the Identity Provider System if the value is \"Unknown\"" + "UNKNOWN": "Desconhecido", + "UNKNOWN_TIP": "Verifique se o usuário tem privilégios administrativos, caso o valor seja \"Unknown\" (desconhecido)." }, "PROJECT": { "PROJECTS": "Projetos", @@ -214,7 +215,7 @@ "ROLE": "Função", "PUBLIC_OR_PRIVATE": "Nível de acesso", "REPO_COUNT": "Quantidade de repositórios", - "CHART_COUNT": "Chart Count", + "CHART_COUNT": "Quantidade de charts", "CREATION_TIME": "Data de criação", "ACCESS_LEVEL": "Nível de acesso", "PUBLIC": "Público", @@ -227,7 +228,7 @@ "PUBLIC_PROJECTS": "Projetos Públicos", "PROJECT": "Projeto", "NEW_PROJECT": "Novo Projeto", - "NAME_TOOLTIP": "Nome do projeto deve conter 1~255 caracteres sendo minusculos, números e ._- e deve iniciar com letras ou números.", + "NAME_TOOLTIP": "Nome do projeto deve conter no máximo 255 caracteres, apenas letras minusculas, números, símbolos ._- e deve iniciar com letras ou números.", "NAME_IS_REQUIRED": "Nome do projeto é obrigatório.", "NAME_ALREADY_EXISTS": "Nome do projeto já existe.", "NAME_IS_ILLEGAL": "Nome do projeto é inválido.", @@ -240,49 +241,48 @@ "CREATED_SUCCESS": "Projeto criado com sucesso.", "DELETED_SUCCESS": "Projeto removido com sucesso.", "TOGGLED_SUCCESS": "Projeto alterado com sucesso.", - "FAILED_TO_DELETE_PROJECT": "Project contains repositories or replication rules or helm-charts cannot be deleted.", - "INLINE_HELP_PUBLIC": "Quando um projeto é marcado como público, qualquer um tem permissões de leitura aos repositórios desse projeto, e o usuário não precisa executar \"docker login\" antes de baixar imagens desse projeto.", + "FAILED_TO_DELETE_PROJECT": "Projeto não pode ser removido porque ainda possui recursos em repositórios, regras de replicação ou helm charts.", + "INLINE_HELP_PUBLIC": "Quando o projeto é público, o acesso de leitura aos repositórios é liberado, incluindo usuários anônimos não autenticados. O usuário não precisa executar \"docker login\" para baixar imagens desse projeto.", "OF": "de", - "COUNT_QUOTA": "Count quota", - "STORAGE_QUOTA": "Storage quota", - "COUNT_QUOTA_TIP": "Please enter an integer between '1' & '100,000,000', '-1' for unlimited", - "STORAGE_QUOTA_TIP": "The upper limit of Storage Quota only takes integer values, capped at '1024TB'. Enter '-1' for unlimited quota", - "QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.", - "TYPE": "Type", + "COUNT_QUOTA": "Limite de quantidade", + "STORAGE_QUOTA": "Limite de armazenamento", + "COUNT_QUOTA_TIP": "Informe um número inteiro entre 1 e 100.000.000. Use -1 para ilimitado.", + "STORAGE_QUOTA_TIP": "Limite de armazenamento. Apenas números inteiros. Valor máximo de 1024TB. Use -1 para ilimitado.", + "QUOTA_UNLIMIT_TIP": "Para não ter limite, utilize -1.", + "TYPE": "Tipo", "PROXY_CACHE": "Proxy Cache", - "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay and Google GCR registries.", - "ENDPOINT": "Endpoint", - "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint", - "NO_PROJECT": "We couldn't find any projects" + "PROXY_CACHE_TOOLTIP": "Habilite para fazer deste projeto um cache local de outros repositórios remotos (registries). O Harbor pode servir de cache apenas para outros repositórios Harbor, Docker Hub, AWS ECR, Azure ACR, Quay, Google GCR e repositórios compatíveis com o protocolo Docker Registry", + "ENDPOINT": "Endereço", + "PROXY_CACHE_ENDPOINT": "Endereço do Proxy Cache" }, "PROJECT_DETAIL": { - "SUMMARY": "Summary", + "SUMMARY": "Resumo", "REPOSITORIES": "Repositórios", "REPLICATION": "Replicação", "USERS": "Membros", "LOGS": "Logs", - "LABELS": "Etiquetas", + "LABELS": "Marcadores", "PROJECTS": "Projetos", "CONFIG": "Configuração", "HELMCHART": "Helm Charts", - "ROBOT_ACCOUNTS": "Robot Accounts", + "ROBOT_ACCOUNTS": "Contas de Automação", "WEBHOOKS": "Webhooks", - "IMMUTABLE_TAG": "Tag Immutability", - "POLICY": "Policy" + "IMMUTABLE_TAG": "Tags Imutáveis", + "POLICY": "Política" }, "PROJECT_CONFIG": { - "REGISTRY": "Registro do Projeto", + "REGISTRY": "Registry do Projeto", "PUBLIC_TOGGLE": "Público", - "PUBLIC_POLICY": "Tornar um registro do projeto público irá fazer todos os repositórios acessíveis a qualquer um.", - "SECURITY": "Segurança do Deployment", - "CONTENT_TRUST_TOGGLE": "Habilitar content trust", - "CONTENT_TRUST_POLCIY": "Permitir o deploy apenas de imagens verificadas.", - "PREVENT_VULNERABLE_TOGGLE": "Prevenir imagens vulneráveis de executar.", + "PUBLIC_POLICY": "Tornar o registry do projeto público tornará todos os repositórios acessíveis sem autenticação.", + "SECURITY": "Segurança de implantação", + "CONTENT_TRUST_TOGGLE": "Habilitar regras de confiança", + "CONTENT_TRUST_POLCIY": "Permitir implantação apenas de imagens verificadas.", + "PREVENT_VULNERABLE_TOGGLE": "Prevenir execução de imagens vulneráveis.", "PREVENT_VULNERABLE_1": "Prevenir imagens com vulnerabilidades de severidade", "PREVENT_VULNERABLE_2": "e acima de serem utilizadas.", "SCAN": "Análise de vulnerabilidades", - "AUTOSCAN_TOGGLE": "Verificar imagens automaticamente após o push", - "AUTOSCAN_POLICY": "Verificar imagens automaticamente quando elas são enviadas ao registry do projeto." + "AUTOSCAN_TOGGLE": "Verificar imagens automaticamente", + "AUTOSCAN_POLICY": "Imagens serão analisadas automaticamente quando enviadas ao registry do projeto." }, "MEMBER": { "NEW_USER": "Adicionar um usuário", @@ -295,20 +295,20 @@ "PROJECT_MAINTAINER": "Mantenedor", "DEVELOPER": "Desenvolvedor", "GUEST": "Visitante", - "LIMITED_GUEST": "Limited Guest", + "LIMITED_GUEST": "Convidado com Limites", "DELETE": "Remover", "ITEMS": "itens", "ACTIONS": "Ações", "USER": " Usuário", "USERS": "Usuários", - "EMAIL": "Email", + "EMAIL": "E-mail", "ADD_USER": "Adicionar usuário", - "NEW_USER_INFO": "Adicionar um usuário como membro desse projeto com a função especificada", + "NEW_USER_INFO": "Adicionar usuário como membro deste projeto, dentro de um perfil específico.", "NEW_GROUP": "Novo grupo", "IMPORT_GROUP": "Adicionar membro ao grupo", - "NEW_GROUP_INFO": "Adicionar um grupo de usuários existente ou selecionar um grupo de usuários do LDAP/AD para membro do projeto", - "ADD_GROUP_SELECT": "Adicionar um grupo de usuários existente como membro do projeto", - "CREATE_GROUP_SELECT": "Adicionar um grupo do LDAP como membro do projeto", + "NEW_GROUP_INFO": "Adicionar um grupo de usuários existentes ou selecionar um grupo de usuários do LDAP/AD", + "ADD_GROUP_SELECT": "Adicionar um grupo de usuários existentes", + "CREATE_GROUP_SELECT": "Adicionar um grupo do LDAP", "LDAP_SEARCH_DN": "Grupo DN do LDAP", "LDAP_SEARCH_NAME": "Nome", "LDAP_GROUP": "Grupo", @@ -336,50 +336,50 @@ }, "ROBOT_ACCOUNT": { "NAME": "Nome", - "PERMISSIONS": "Permissions", + "PERMISSIONS": "Permissões", "TOKEN": "Token", - "NEW_ROBOT_ACCOUNT": "Novo robô conta", - "ENABLED_STATE": "Enabled state", - "CREATETION": "Created time", - "EXPIRATION": "Expiration", - "NUMBER_REQUIRED": "Field is required and should be an integer other than 0.", - "TOKEN_EXPIRATION": "Robot Token Expiration (Days)", + "NEW_ROBOT_ACCOUNT": "Nova conta de automação", + "ENABLED_STATE": "Habilitado", + "CREATETION": "Criação", + "EXPIRATION": "Expiração", + "NUMBER_REQUIRED": "Campo obrigatório. Não pode ser 0 (zero).", + "TOKEN_EXPIRATION": "Expiração do token de automação (em dias)", "DESCRIPTION": "Descrição", "ACTION": "AÇÃO", "EDIT": "Editar", "ITEMS": "itens", "OF": "de", - "DISABLE_ACCOUNT": "Desactivar a conta", + "DISABLE_ACCOUNT": "Desativar conta", "ENABLE_ACCOUNT": "Ativar conta", "DELETE": "Remover", - "CREAT_ROBOT_ACCOUNT": "CRIA robô conta", - "PERMISSIONS_ARTIFACT": "Artifact", + "CREAT_ROBOT_ACCOUNT": "Criar conta de automação", + "PERMISSIONS_ARTIFACT": "Artefato", "PERMISSIONS_HELMCHART": "Helm Chart (Chart Museum)", "PUSH": "Push", "PULL": "Pull", - "FILTER_PLACEHOLDER": "Filtro robot accounts", - "ROBOT_NAME": "Não Pode conter caracteres especiais(~#$%) e comprimento máximo deveria ser 255 caracteres.", - "ACCOUNT_EXISTING": "Robô conta já existe.", - "ALERT_TEXT": "É só copiar o token de acesso Pessoal não VAI ter outra oportunidade.", - "CREATED_SUCCESS": "Created '{{param}}' successfully.", - "COPY_SUCCESS": "Copy secret successfully of '{{param}}'", - "DELETION_TITLE": "Confirmar a remoção do robô Contas", + "FILTER_PLACEHOLDER": "Filtro contas de automação", + "ROBOT_NAME": "Não Pode conter caracteres especiais(~#$%). Máximo de 255 caracteres.", + "ACCOUNT_EXISTING": "Conta de automação já existe.", + "ALERT_TEXT": "Copie AGORA e resguarde o valor do token de acesso. Não haverá outra oportunidade.", + "CREATED_SUCCESS": "'{{param}}' criado com sucesso.", + "COPY_SUCCESS": "Cópia de segredo feita com sucesso: '{{param}}'", + "DELETION_TITLE": "Confirmar a remoção da conta", "DELETION_SUMMARY": "Você quer remover a regra {{param}}?", - "PULL_IS_MUST": "Pull permission is checked by default and can not be modified.", - "EXPORT_TO_FILE": "export to file", - "EXPIRES_AT": "Expires At", - "EXPIRATION_TOOLTIP": "If not set, the expiration time of system configuration will be used", - "INVALID_VALUE": "The value of the expiration time is invalid", - "NEVER_EXPIRED": "Never Expired", - "NAME_PREFIX": "Robot Name Prefix", - "NAME_PREFIX_REQUIRED": "Robot name prefix is required" + "PULL_IS_MUST": "Permissão de 'pull' é obrigatória e não pode ser desligada.", + "EXPORT_TO_FILE": "exportar para arquivo", + "EXPIRES_AT": "Expira em", + "EXPIRATION_TOOLTIP": "Se não informado, a configuração do sistema será usada.", + "INVALID_VALUE": "Valor do tempo de expiração inválido.", + "NEVER_EXPIRED": "Não expirar nunca", + "NAME_PREFIX": "Prefixo para contas de automação", + "NAME_PREFIX_REQUIRED": "Prefixo é obrigatório." }, "GROUP": { "GROUP": "Grupo", "GROUPS": "Grupos", "IMPORT_LDAP_GROUP": "Importar grupo do LDAP", - "IMPORT_HTTP_GROUP": "New HTTP Group", - "IMPORT_OIDC_GROUP": "New OIDC Group", + "IMPORT_HTTP_GROUP": "Novo Grupo HTTP", + "IMPORT_OIDC_GROUP": "Novo Grupo OIDC", "ADD": "Novo Grupo", "EDIT": "Editar", "DELETE": "Remover", @@ -396,67 +396,67 @@ "OIDC_TYPE": "OIDC", "OF": "de", "ITEMS": "itens", - "NEW_MEMBER": "New Group Member", - "NEW_USER_INFO": "Add a group to be a member of this project with specified role", - "ROLE": "Role", - "SYS_ADMIN": "System Admin", - "PROJECT_ADMIN": "Project Admin", - "PROJECT_MAINTAINER": "Maintainer", - "DEVELOPER": "Developer", - "GUEST": "Guest", - "LIMITED_GUEST": "Limited Guest", - "DELETION_TITLE": "Confirm group members deletion", - "DELETION_SUMMARY": "Do you want to delete group member(s) {{param}}?" + "NEW_MEMBER": "Novo Membro", + "NEW_USER_INFO": "Adicionar um grupo com determinado perfil como membro deste projeto.", + "ROLE": "Perfil", + "SYS_ADMIN": "Administrador do Sistema", + "PROJECT_ADMIN": "Administrador do Projeto", + "PROJECT_MAINTAINER": "Mantenedor", + "DEVELOPER": "Desenvolvedor", + "GUEST": "Convidado", + "LIMITED_GUEST": "Convidado Limitado", + "DELETION_TITLE": "Confirmação para remover membros", + "DELETION_SUMMARY": "Tem certeza de que deseja remover: {{param}}?" }, "WEBHOOK": { - "EDIT_BUTTON": "EDIT", - "ENABLED_BUTTON": "ENABLE", - "DISABLED_BUTTON": "DISABLE", + "EDIT_BUTTON": "EDITAR", + "ENABLED_BUTTON": "HABILITAR", + "DISABLED_BUTTON": "DESABILITAR", "TYPE": "Webhook", - "STATUS": "Status", - "CREATED": "Created", - "ENABLED": "Enabled", - "DISABLED": "Disabled", - "OF": "of", - "ITEMS": "items", - "LAST_TRIGGERED": "Last Triggered", - "EDIT_WEBHOOK": "Edit Webhook", - "ADD_WEBHOOK": "Add Webhook", - "CREATE_WEBHOOK": "Getting started with webhooks", - "EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications", - "CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.", - "VERIFY_REMOTE_CERT_TOOLTIP": "Determine whether the webhook should verify the certificate of a remote url Uncheck this box when the remote url uses a self-signed or untrusted certificate.", - "ENDPOINT_URL": "Endpoint URL", - "URL_IS_REQUIRED": "Endpoint URL is required.", - "AUTH_HEADER": "Auth Header", - "VERIFY_REMOTE_CERT": "Verify Remote Certificate", - "TEST_ENDPOINT_BUTTON": "TEST ENDPOINT", - "CANCEL_BUTTON": "CANCEL", - "SAVE_BUTTON": "SAVE", - "TEST_ENDPOINT_SUCCESS": "Connection tested successfully.", - "TEST_ENDPOINT_FAILURE": "Failed to ping endpoint.", - "ENABLED_WEBHOOK_TITLE": "Enable Webhook", - "ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhook {{name}}?", - "DISABLED_WEBHOOK_TITLE": "Disable Webhook", - "DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhook {{name}}?", - "DELETE_WEBHOOK_TITLE": "Delete Webhook(s)", - "DELETE_WEBHOOK_SUMMARY": "Do you want to delete webhook(s) {{names}}?", + "STATUS": "Situação", + "CREATED": "Criado", + "ENABLED": "Habilitado", + "DISABLED": "Desabilitado", + "OF": "de", + "ITEMS": "itens", + "LAST_TRIGGERED": "Último Disparo", + "EDIT_WEBHOOK": "Editar Webhook", + "ADD_WEBHOOK": "Adicionar Webhook", + "CREATE_WEBHOOK": "Criação de webhooks", + "EDIT_WEBHOOK_DESC": "Informe o endereço que receberá as notificações via webhook", + "CREATE_WEBHOOK_DESC": "Informe o endereço e credenciais de acesso ao servidor onde o webhook está hospedado.", + "VERIFY_REMOTE_CERT_TOOLTIP": "Desmarque se o webhook é servido com um certificado auto-assinado ou não confiável.", + "ENDPOINT_URL": "Endereço (URL)", + "URL_IS_REQUIRED": "Endereço é um campo obrigatório.", + "AUTH_HEADER": "Cabeçalho de Autenticação", + "VERIFY_REMOTE_CERT": "Validar certificado remoto", + "TEST_ENDPOINT_BUTTON": "TESTAR ENDEREÇO", + "CANCEL_BUTTON": "CANCELAR", + "SAVE_BUTTON": "SALVAR", + "TEST_ENDPOINT_SUCCESS": "Teste de conexão realizado com sucesso.", + "TEST_ENDPOINT_FAILURE": "Falha no teste de conexão.", + "ENABLED_WEBHOOK_TITLE": "Habilitar Webhook", + "ENABLED_WEBHOOK_SUMMARY": "Deseja habilitar o webook {{name}}?", + "DISABLED_WEBHOOK_TITLE": "Desabilitar Webhook", + "DISABLED_WEBHOOK_SUMMARY": "Deseja desabilitar o webhook {{name}}?", + "DELETE_WEBHOOK_TITLE": "Remover Webhook(s)", + "DELETE_WEBHOOK_SUMMARY": "Deseja remover o(s) webhook(s) {{names}}?", "WEBHOOKS": "Webhooks", - "NEW_WEBHOOK": "New Webhook", - "ENABLE": "Enable", - "DISABLE": "Disable", - "NAME": "Name", - "TARGET": "Endpoint URL", - "EVENT_TYPES": "Event types", - "DESCRIPTION": "Description", - "NO_WEBHOOK": "No Webhook", - "LAST_TRIGGER": "Last Trigger", - "WEBHOOK_NAME": "Webhook Name", - "NO_TRIGGER": "No Trigger", - "NAME_REQUIRED": "Name is required", - "NOTIFY_TYPE": "Notify Type", - "EVENT_TYPE": "Event Type", - "EVENT_TYPE_REQUIRED": "Require at least one event type" + "NEW_WEBHOOK": "Criar Webhook", + "ENABLE": "Habilitar", + "DISABLE": "Desabilitar", + "NAME": "Nome", + "TARGET": "Endereço (URL)", + "EVENT_TYPES": "Tipos de eventos", + "DESCRIPTION": "Descrição", + "NO_WEBHOOK": "Sem Webhook", + "LAST_TRIGGER": "Último Disparo", + "WEBHOOK_NAME": "Nome do Webhook", + "NO_TRIGGER": "Sem gatilho", + "NAME_REQUIRED": "Nome é obrigatório", + "NOTIFY_TYPE": "Tipo de Notificação", + "EVENT_TYPE": "Tipo de Evento", + "EVENT_TYPE_REQUIRED": "Pelo menos um tipo de evento é obrigatório" }, "AUDIT_LOG": { "USERNAME": "Nome do usuário", @@ -467,7 +467,7 @@ "TIMESTAMP": "Timestamp", "ALL_OPERATIONS": "Todas as Operações", "PULL": "Obter", - "PUSH": "Enviar", + "PUSH": "Push", "CREATE": "Criar", "DELETE": "Remover", "OTHERS": "Outros", @@ -478,48 +478,48 @@ "INVALID_DATE": "Data inválida.", "OF": "de", "NOT_FOUND": "Nós não encontramos nenhum registro!", - "RESOURCE": "Resource", - "RESOURCE_TYPE": "Resource Type" + "RESOURCE": "Recurso", + "RESOURCE_TYPE": "Tipo de Recurso" }, "REPLICATION": { - "YES": "Yes", - "SECONDS": "Seconds", - "MINUTES": "Minutes", - "HOURS": "Hours", - "MONTH": "Month", - "DAY_MONTH": "Day of month", - "DAY_WEEK": "Day of week", - "CRON_TITLE": "Pattern description for cron '* * * * * *'.The cron string is based on UTC time", - "FIELD_NAME": "Field name", - "MANDATORY": "Mandatory?", - "ALLOWED_VALUES": "Allowed values", - "ALLOWED_CHARACTERS": "Allowed special characters", + "YES": "Sim", + "SECONDS": "Segundo", + "MINUTES": "Minuto", + "HOURS": "Hora", + "MONTH": "Mês", + "DAY_MONTH": "Dia do mês", + "DAY_WEEK": "Dia da semana", + "CRON_TITLE": "Padrão de agendamento cron '* * * * * *'. Horário UTC presumido.", + "FIELD_NAME": "Nome do campo", + "MANDATORY": "Obrigatório?", + "ALLOWED_VALUES": "Valores permitidos", + "ALLOWED_CHARACTERS": "Permitir caracteres especiais", "TOTAL": "Total", - "OVERRIDE": "Override", - "ENABLED_RULE": "Enable rule", - "OVERRIDE_INFO": "Override", - "CURRENT": "current", - "FILTER_PLACEHOLDER": "Filter Tasks", + "OVERRIDE": "Sobrescrever", + "ENABLED_RULE": "Habiltar regra", + "OVERRIDE_INFO": "Sobrescrever", + "CURRENT": "atual", + "FILTER_PLACEHOLDER": "Filtro de Tarefas", "STOP_TITLE": "Confirme as execuções de parada", "BOTH": "both", - "PLEASE_SELECT": "select an option", - "STOP_SUCCESS": "Stop Execution {{param}} Successful", + "PLEASE_SELECT": "seleciona uma opção", + "STOP_SUCCESS": "Execução {{param}} parada com sucesso", "STOP_SUMMARY": "Você quer parar as execuções? {{param}}?", - "TASK_ID": "Task ID", - "RESOURCE_TYPE": "Resource Type", - "SOURCE": "Source", - "DESTINATION": "Destination", - "POLICY": "Policy", - "DURATION": "Duration", - "SUCCESS_RATE": "Success Rate", - "SUCCESS": "SUCCESS", - "FAILURE": "FAILURE", - "IN_PROGRESS": "IN PROGRESS", - "STOP_EXECUTIONS": "Stop Execution", + "TASK_ID": "ID de Tarefa", + "RESOURCE_TYPE": "Tipo de Recurso", + "SOURCE": "Fonte", + "DESTINATION": "Destino", + "POLICY": "Política", + "DURATION": "Duração", + "SUCCESS_RATE": "Taxa de Sucesso", + "SUCCESS": "SUCCESSO", + "FAILURE": "FALHA", + "IN_PROGRESS": "EM ANDAMENTO", + "STOP_EXECUTIONS": "Parar Execução", "ID": "ID", "REPLICATION_RULE": "Regra de replicação", "NEW_REPLICATION_RULE": "Nova regra de replicação", - "ENDPOINTS": "Endpoints", + "ENDPOINTS": "Endereços", "FILTER_POLICIES_PLACEHOLDER": "Filtrar regras", "FILTER_EXECUTIONS_PLACEHOLDER": "Execuções de Filtro", "DELETION_TITLE": "Confirmar remoção de regras", @@ -527,8 +527,8 @@ "REPLICATION_TITLE": "Confirmar regras de replicação", "REPLICATION_SUMMARY": "Você quer replicar as regras {{param}}?", "DELETION_TITLE_FAILURE": "falha ao remover a regra de Remoção", - "DELETION_SUMMARY_FAILURE": "possui status pendente/executando/re-executando", - "REPLICATE_SUMMARY_FAILURE": "possui status pendente/executando", + "DELETION_SUMMARY_FAILURE": "está pendente/executando/re-executando", + "REPLICATE_SUMMARY_FAILURE": "está pendente/executando", "FILTER_TARGETS_PLACEHOLDER": "Filtrar Endpoints", "DELETION_TITLE_TARGET": "Confirmar remoção de Endpoint", "DELETION_SUMMARY_TARGET": "Você quer remover o Endpoint {{param}}?", @@ -546,35 +546,35 @@ "DESCRIPTION": "Descrição", "ENABLE": "Habilitar", "DISABLE": "Desabilitar", - "REPLICATION_MODE": "Replication Mode", - "SRC_REGISTRY": "Source registry", - "DESTINATION_NAMESPACE": "Destination registry:Namespace", - "DESTINATION_NAME_IS_REQUIRED": "Nome do Endpoint é obrigatório.", - "NEW_DESTINATION": "Novo Endpoint", - "DESTINATION_URL": "URL do Endpoint", - "DESTINATION_URL_IS_REQUIRED": "URL do Endpoint é obrigatória.", + "REPLICATION_MODE": "Modo de Replicação", + "SRC_REGISTRY": "Registry de origem", + "DESTINATION_NAMESPACE": "Registry de destino no formato 'registry:mamespace'", + "DESTINATION_NAME_IS_REQUIRED": "Nome é um campo obrigatório", + "NEW_DESTINATION": "Novo Destino", + "DESTINATION_URL": "Endereço do destino (URL)", + "DESTINATION_URL_IS_REQUIRED": "Endereço do destino é um campo obrigatório.", "DESTINATION_USERNAME": "Nome de usuário", "DESTINATION_PASSWORD": "Senha", - "ALL_STATUS": "Todos os Status", + "ALL_STATUS": "Todos", "ENABLED": "Habilitado", "DISABLED": "Desabilitado", "LAST_START_TIME": "Ultima hora de início", "ACTIVATION": "Ativação", - "REPLICATION_EXECUTION": "ExecTarefa de Replicaçãoution", + "REPLICATION_EXECUTION": "Tarefa de Replicação", "REPLICATION_EXECUTIONS": "Tarefas de Replicação", "STOPJOB": "Parar", - "END_TIME": "End Time", + "END_TIME": "Término", "ALL": "Todas", "PENDING": "Pendentes", "RUNNING": "Executando", "ERROR": "Erro", "RETRYING": "Tentando novamente", - "STOPPED": "STOPPED", + "STOPPED": "PARADO", "FINISHED": "Finalizada", "CANCELED": "Cancelada", "SIMPLE": "Simples", "ADVANCED": "Avançado", - "STATUS": "Status", + "STATUS": "Situação", "OPERATION": "Operação", "CREATION_TIME": "Hora de início", "UPDATE_TIME": "Hora de atualização", @@ -595,10 +595,10 @@ "JOB_PLACEHOLDER": "Não foi possível encontrar nenhuma tarefa de replicação!", "NO_ENDPOINT_INFO": "Por favor adicione antes um endpoint", "NO_PROJECT_INFO": "Esse projeto não existe", - "SOURCE_RESOURCE_FILTER": "Source resource filter", + "SOURCE_RESOURCE_FILTER": "Filtro de recursos da origem", "SCHEDULED": "Agendado", "MANUAL": "Manual", - "EVENT_BASED": "Event Based", + "EVENT_BASED": "Baseado em Eventos", "DAILY": "Diário", "WEEKLY": "Semanal", "SETTING": "Opções", @@ -608,80 +608,80 @@ "TRIGGER_MODE": "Modo de disparo", "SOURCE_PROJECT": "Projeto de origem", "REPLICATE": "Replicar", - "DELETE_REMOTE_IMAGES": "Remover resources remotas quando removido localmente", - "DELETE_ENABLED": "Enabled this policy", + "DELETE_REMOTE_IMAGES": "Remover recursos remotos quando removidos localmente", + "DELETE_ENABLED": "Habilitar política", "NEW": "Novo", - "NAME_TOOLTIP": "nome da regra de replicação deve conter ao menos 2 caracteres sendo caracteres minusculos, números e ._- e devem iniciar com letras e números.", - "DESTINATION_NAME_TOOLTIP": "Destination name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", + "NAME_TOOLTIP": "nome da regra de replicação deve conter ao menos 2 caracteres, apenas letras minusculas, números, símbolos ._- e deve iniciar com letras ou números.", + "DESTINATION_NAME_TOOLTIP": "Nome destino deve conter no mínimo 2 caracteres, apenas letras minúsculas, números, símbolos ._- deve iniciar com letras ou números", "ACKNOWLEDGE": "Reconhecer", - "RULE_DISABLED": "Essa regra foi desabilitada pois uma label usada no seu filtro foi removida. \n Edite a regra e atualize seu filtro para habilitá-la.", - "REPLI_MODE": "Replication mode", - "SOURCE_REGISTRY": "Source registry", - "SOURCE_NAMESPACES": "Source namespaces", - "DEST_REGISTRY": "Destination registry", - "DEST_NAMESPACE": "Destination namespace", - "NAMESPACE_TOOLTIP": "Namespace name should be at least 2 characters long with lower case characters, numbers and ._-/ and must be start with characters or numbers.", + "RULE_DISABLED": "Essa regra foi desabilitada, pois um marcador usado em seu filtro foi removido. \n Edite a regra e atualize o filtro para habilitá-la.", + "REPLI_MODE": "Modo de replicação", + "SOURCE_REGISTRY": "Registry de origem", + "SOURCE_NAMESPACES": "Namespace de origem", + "DEST_REGISTRY": "Registry de destino", + "DEST_NAMESPACE": "Namespace de destino", + "NAMESPACE_TOOLTIP": "Nome destino deve conter no mínimo 2 caracteres, apenas letras minúsculas, números, símbolos ._-/ e deve iniciar com letras ou números", "TAG": "Tag", - "LABEL": "Label", - "RESOURCE": "Resource", - "ENABLE_TITLE": "Enable rule", - "ENABLE_SUMMARY": "Do you want to enable rule {{param}}?", - "DISABLE_TITLE": "Disable rule", - "DISABLE_SUMMARY": "Do you want to disable rule {{param}}?", - "ENABLE_SUCCESS": "Enabled rule successfully", - "ENABLE_FAILED": "Enabling rule failed", - "DISABLE_SUCCESS": "Disabled rule successfully", - "DISABLE_FAILED": "Disabling rule failed", - "DES_REPO_FLATTENING": "Destination Repository Flattening", + "LABEL": "Marcador", + "RESOURCE": "Recurso", + "ENABLE_TITLE": "Habilitar regra", + "ENABLE_SUMMARY": "Deseja habilitar a regra {{param}}?", + "DISABLE_TITLE": "Desabilitar regra", + "DISABLE_SUMMARY": "Deseja desabilitar a regra {{param}}?", + "ENABLE_SUCCESS": "Regra habilitada com sucesso", + "ENABLE_FAILED": "Falha ao habilitar a regra", + "DISABLE_SUCCESS": "Regra desabilitada com sucesso", + "DISABLE_FAILED": "Falha ao desabilitar a regra", + "DES_REPO_FLATTENING": "Nivelar nomes no repositório de destino", "NAMESPACE": "Namespace", - "REPO_FLATTENING": "Flattening", - "NO_FLATTING": "No Flatting", - "FLATTEN_LEVEL_1": "Flatten 1 Level", - "FLATTEN_LEVEL_2": "Flatten 2 Levels", - "FLATTEN_LEVEL_3": "Flatten 3 Levels", - "FLATTEN_ALL": "Flatten All Levels", - "FLATTEN_LEVEL_TIP": "Reduce the nested repository structure when copying images. Assuming that the nested repository structure is 'a/b/c/d/img' and the destination namespace is 'ns', the corresponding results of each item are as below:", - "FLATTEN_LEVEL_TIP_ALL": "'Flatten All Levels'(Used prior v2.3): 'a/b/c/d/img' -> 'ns/img'", - "FLATTEN_LEVEL_TIP_NO": "'No Flatting': 'a/b/c/d/img' -> 'ns/a/b/c/d/img", - "FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'", - "FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'", - "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", - "NOTE": "Notes: The Chartmuseum Charts only support the repository structure with 2 path components: 'a/chart'" + "REPO_FLATTENING": "Nivelamento", + "NO_FLATTING": "Não nivelar", + "FLATTEN_LEVEL_1": "Nivelar 1 posição", + "FLATTEN_LEVEL_2": "Nivelar 2 posições", + "FLATTEN_LEVEL_3": "Nivelar 3 posições", + "FLATTEN_ALL": "Nivelar todas as posições", + "FLATTEN_LEVEL_TIP": "Reduzir os nomes de repositórios aninhados ao copiar as imagens. Presumindo que o nome do repositório remoto é 'a/b/c/d/img' e o namespace de destino é 'ns', os resultados para cada opção seriam:", + "FLATTEN_LEVEL_TIP_ALL": "'Nivelar todas as posições' (anterior a v2.3): 'a/b/c/d/img' -> 'ns/img'", + "FLATTEN_LEVEL_TIP_NO": "'Não nivelar': 'a/b/c/d/img' -> 'ns/a/b/c/d/img", + "FLATTEN_LEVEL_TIP_1": "'Nivelar 1 posição' (padrão): 'a/b/c/d/img' -> 'ns/b/c/d/img'", + "FLATTEN_LEVEL_TIP_2": "'Nivelar 2 posições': 'a/b/c/d/img' -> 'ns/c/d/img'", + "FLATTEN_LEVEL_TIP_3": "'Nivelar 3 posições': 'a/b/c/d/img' -> 'ns/d/img'", + "NOTE": "Nota: Chartmuseum suporta helm charts apenas com 2 níveis: 'a/chart'" }, "DESTINATION": { - "NEW_ENDPOINT": "Novo Endpoint", - "PROVIDER": "Provider", - "ENDPOINT": "Endpoint", + "NEW_ENDPOINT": "Novo Endereço", + "PROVIDER": "Provedor", + "ENDPOINT": "Endereço", "NAME": "Nome", - "NAME_IS_REQUIRED": "Nome do Endpoint é obrigatório.", + "NAME_IS_REQUIRED": "Nome do endereço é obrigatório.", "URL": "URL do Endpoint", - "URL_IS_REQUIRED": "URL do Endpoint URL é obrigatória.", + "URL_IS_REQUIRED": "URL é obrigatória.", "AUTHENTICATION": "Autenticação", "ACCESS_ID": "ID de acesso", - "ACCESS_SECRET": "Secreto de acceso", - "STATUS": "Segredo de acesso", + "ACCESS_SECRET": "Segredo de acceso", + "STATUS": "Situação", "TEST_CONNECTION": "Testar Conexão", - "TITLE_EDIT": "Editar Endpoint", - "TITLE_ADD": "Novo ponto final do registro", + "TITLE_EDIT": "Editar Endereço", + "TITLE_ADD": "Novo endereço de Registry", "EDIT": "Editar", "DELETE": "Remover", "TESTING_CONNECTION": "Testando Conexão...", "TEST_CONNECTION_SUCCESS": "Conexão testada com sucesso.", - "TEST_CONNECTION_FAILURE": "Falha ao pingar o endpoint.", + "TEST_CONNECTION_FAILURE": "Falha ao testar conexão.", "CONFLICT_NAME": "O nome do terminal já existe.", - "INVALID_NAME": "Nome do Endpoint inválido.", - "FAILED_TO_GET_TARGET": "Falha ao obter endpoint.", + "INVALID_NAME": "Nome de endereço inválido.", + "FAILED_TO_GET_TARGET": "Falha ao obter recursos do endereço.", "CREATION_TIME": "Data de criação", "OF": "de", "ITEMS": "itens", - "CREATED_SUCCESS": "Endpoint criado com sucesso.", - "UPDATED_SUCCESS": "Endpoint atualizado com sucesso.", - "DELETED_SUCCESS": "Endpoint removido com sucesso.", - "DELETED_FAILED": "Falha em endpoints removidos.", - "CANNOT_EDIT": "Endpoint não pode ser alterado enquando a regra de replicação estiver ativa.", - "FAILED_TO_DELETE_TARGET_IN_USED": "Falha ao remover endpoint em uso.", - "PLACEHOLDER": "Não foi possível encontrar nenhum endpoint!", - "DEPRECATED": "Helm Hub is moving to Artifact Hub" + "CREATED_SUCCESS": "Endereço criado com sucesso.", + "UPDATED_SUCCESS": "Endereço atualizado com sucesso.", + "DELETED_SUCCESS": "Endereço removido com sucesso.", + "DELETED_FAILED": "Falha ao remover endereço.", + "CANNOT_EDIT": "Endereço não pode ser alterado enquando a regra de replicação estiver ativa.", + "FAILED_TO_DELETE_TARGET_IN_USED": "Falha ao remover endereço em uso.", + "PLACEHOLDER": "Não há endereços cadastrados", + "DEPRECATED": "Helm Hub está sendo movido para Artifact Hub" }, "REPOSITORY": { "COPY_DIGEST_ID": "Copiar Digest", @@ -693,26 +693,26 @@ "ARTIFACTS_COUNT": "Artifacts", "PULL_COUNT": "Pulls", "PULL_COMMAND": "Comando de Pull", - "PULL_TIME": "Pull Time", - "PUSH_TIME": "Push Time", - "IMMUTABLE": "Immutable", + "PULL_TIME": "Horário de Envio", + "PUSH_TIME": "Horário de Recebimento", + "IMMUTABLE": "Imutável", "MY_REPOSITORY": "Meu Repositório", "PUBLIC_REPOSITORY": "Repositório Público", "DELETION_TITLE_REPO": "Confirmar remoção de repositório", "DELETION_TITLE_REPO_SIGNED": "Repositório não pode ser removido", - "DELETION_SUMMARY_REPO_SIGNED": "Repositório '{{repoName}}' não pode ser removido pois existem as seguintes imagens assinadas.\n{{signedImages}} \nVocê deve remover a assinatura de todas as imagens assinadas antes de remover o repositório!", + "DELETION_SUMMARY_REPO_SIGNED": "Repositório '{{repoName}}' não pode ser removido pois possui imagens assinadas.\n{{signedImages}} \nVocê deve remover a assinatura de todas as imagens assinadas antes de remover o repositório!", "DELETION_SUMMARY_REPO": "Você deseja remover o repositório {{repoName}}?", - "DELETION_TITLE_ARTIFACT": "Confirm Artifact Deletion", - "DELETION_SUMMARY_ARTIFACT": "Do you want to delete artifact {{param}}? If you delete this artifact, all tags referenced by the digest will also be deleted.", + "DELETION_TITLE_ARTIFACT": "Confirmar Remoção de Artefato", + "DELETION_SUMMARY_ARTIFACT": "Deseja remover o artefato {{param}}? Ao remover, todas as tags que referenciam o digest desta imagem também serão removidos.", "DELETION_TITLE_TAG": "Confirmar remoção de Tag", - "DELETION_SUMMARY_TAG": "Você quer remover a Tag {{param}}? ", + "DELETION_SUMMARY_TAG": "Deseja remover a Tag {{param}}? ", "DELETION_TITLE_TAG_DENIED": "Tags assinadas não podem ser removidas", "DELETION_SUMMARY_TAG_DENIED": "A tag deve ser removida do Notary antes de ser apagada.\nRemova do Notary com o seguinte comando:\n", "TAGS_NO_DELETE": "Remover é proibido em modo somente leitura.", "FILTER_FOR_REPOSITORIES": "Filtrar repositórios", "TAG": "Tag", - "ARTIFACT": "Aarifact", - "ARTIFACTS": "Artifacts", + "ARTIFACT": "Artefato", + "ARTIFACTS": "Artefatos", "SIZE": "Tamanho", "VULNERABILITY": "Vulnerabilidade", "SIGNED": "Assinada", @@ -736,17 +736,17 @@ "INFO": "Info", "NO_INFO": "Nenhuma descrição de informação para esse repositório", "IMAGE": "Imagens", - "LABELS": "Labels", - "ADD_LABEL_TO_IMAGE": "Adicionar labels a essa imagem", - "FILTER_BY_LABEL": "Filtrar imagens por label", - "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", - "ADD_LABELS": "Adicionar Labels", - "RETAG": "Copy", + "LABELS": "Marcadores", + "ADD_LABEL_TO_IMAGE": "Adicionar marcadores a esta imagem", + "FILTER_BY_LABEL": "Filtrar imagens por marcadores", + "FILTER_ARTIFACT_BY_LABEL": "Filtrar por marcador", + "ADD_LABELS": "Adicionar Marcadores", + "RETAG": "Copiar", "ACTION": "AÇÃO", - "DEPLOY": "DEPLOY", + "DEPLOY": "IMPLANTAR", "ADDITIONAL_INFO": "Adicionar informação adicional", - "MARKDOWN": "Styling with Markdown is supported", - "LAST_MODIFIED": "Last Modified Time" + "MARKDOWN": "Estilização com Markdown suportada", + "LAST_MODIFIED": "Data da última alteração" }, "HELM_CHART": { "HELMCHARTS": "Charts", @@ -802,33 +802,33 @@ "READY": "Pronto", "NOT_READY": "Não Pronto", "ADD_LABEL_TO_CHART_VERSION": "Add labels to this chart version", - "STATUS": "Status" + "STATUS": "Situação" }, "SUMMARY": { - "QUOTAS": "quotas", - "PROJECT_REPOSITORY": "Repositories", + "QUOTAS": "cotas", + "PROJECT_REPOSITORY": "Repositórios", "PROJECT_HELM_CHART": "Helm Chart", - "PROJECT_MEMBER": "Members", - "PROJECT_QUOTAS": "Quotas", - "ARTIFACT_COUNT": "Artifact count", - "STORAGE_CONSUMPTION": "Storage consumption", - "ADMIN": "Admin(s)", - "MAINTAINER": "Maintainer(s)", - "DEVELOPER": "Developer(s)", - "GUEST": "Guest(s)", - "LIMITED_GUEST": "Limited guest(s)", - "SEE_ALL": "SEE ALL" + "PROJECT_MEMBER": "Membrs", + "PROJECT_QUOTAS": "Cotas", + "ARTIFACT_COUNT": "Contagem de artefatos", + "STORAGE_CONSUMPTION": "Consumo de Armazenamento", + "ADMIN": "Admininstrador(es)", + "MAINTAINER": "Mantenedor(es)", + "DEVELOPER": "Desenvolvedor(es)", + "GUEST": "Convidado(s)", + "LIMITED_GUEST": "Convidado(s) Limitado(s)", + "SEE_ALL": "VER TODOS" }, "ALERT": { - "FORM_CHANGE_CONFIRMATION": "Algumas alterações ainda não foram salvas. Você deseja cancelar?" + "FORM_CHANGE_CONFIRMATION": "Algumas alterações ainda não foram salvas. Deseja cancelar?" }, "RESET_PWD": { "TITLE": "Redefinir senha", "CAPTION": "Insira seu email para redefinir sua senha", - "EMAIL": "Email", - "SUCCESS": "Mail com link para redefinição de senha encaminhado com sucesso. Você pode fechar essa tela e verificar sua caixa de entrada.", + "EMAIL": "E-mail", + "SUCCESS": "E-mail com link para redefinição de senha encaminhado com sucesso. Confira sua caixa de entrada ou caixa de Spam.", "CAPTION2": "Insira sua nova senha", - "RESET_OK": "Senha redefinida com sucesso. Pressione OK para logar-se com sua nova senha." + "RESET_OK": "Senha redefinida com sucesso. Pressione OK para entrar com a nova senha." }, "RECENT_LOG": { "SUB_TITLE": "Exibir", @@ -839,26 +839,26 @@ "TITLE": "Configuração", "AUTH": "Autenticação", "REPLICATION": "Replicação", - "EMAIL": "Email", + "EMAIL": "E-mail", "LABEL": "Labels", "REPOSITORY": "Repositório", "REPO_READ_ONLY": "Repositório somente leitura", "SYSTEM": "Configurações do Sistema", - "PROJECT_QUOTAS": "Project Quotas", + "PROJECT_QUOTAS": "Cotas de Projeto", "VULNERABILITY": "Vulnerabilidade", - "GC": "Garbage Collection", - "CONFIRM_TITLE": "Confirme para cancelar", - "CONFIRM_SUMMARY": "Algumas alterações não foram salvas. Você deseja descartar elas?", + "GC": "Limpeza (GC)", + "CONFIRM_TITLE": "Confirmação de cancelamento", + "CONFIRM_SUMMARY": "Algumas alterações não foram salvas. Deseja descartá-las?", "SAVE_SUCCESS": "Configuração salva com sucesso.", - "MAIL_SERVER": "Servidor de Email", - "MAIL_SERVER_PORT": "Porta do Servidor de Email", - "MAIL_USERNAME": "Nome de usuário do Servidor de Email", - "MAIL_PASSWORD": "Senha do servidor de Email", + "MAIL_SERVER": "Servidor de E-mail", + "MAIL_SERVER_PORT": "Porta do Servidor de E-mail", + "MAIL_USERNAME": "Nome de usuário do Servidor de E-mail", + "MAIL_PASSWORD": "Senha do servidor de E-mail", "MAIL_FROM": "Email de", "MAIL_SSL": "Email SSL", "MAIL_INSECURE": "Verificar certificados", - "INSECURE_TOOLTIP": "Determina se o certificado do servidor de Email deve ser verificado. Desmarque esse item quando o servidor de Email utiliza um certificado auto-assinado.", - "SSL_TOOLTIP": "Habilitar SSL para conexão ao servidor de Email", + "INSECURE_TOOLTIP": "Determina se o certificado do servidor de Email deve ser verificado. Desmarque esse item quando o servidor de Email utiliza um certificado auto-assinado ou não confiável.", + "SSL_TOOLTIP": "Habilitar SSL para conexão ao servidor de E-mail", "VERIFY_REMOTE_CERT": "Verificar Certificado remoto", "TOKEN_EXPIRATION": "Expiração do Token (Minutos)", "AUTH_MODE": "Modo de autenticação", @@ -867,7 +867,7 @@ "AUTH_MODE_DB": "Banco de Dados", "AUTH_MODE_LDAP": "LDAP", "AUTH_MODE_UAA": "UAA", - "AUTH_MODE_HTTP": "Http Auth", + "AUTH_MODE_HTTP": "HTTP Auth", "AUTH_MODE_OIDC": "OIDC", "SCOPE_BASE": "Base", "SCOPE_ONE_LEVEL": "OneLevel", @@ -876,29 +876,29 @@ "PRO_CREATION_ADMIN": "Apenas Administradores", "ROOT_CERT": "Certificado Raiz do Registry", "ROOT_CERT_LINK": "Download", - "REGISTRY_CERTIFICATE": "Registry certificate", - "NO_CHANGE": "Save abort because nothing changed", + "REGISTRY_CERTIFICATE": "Certificado do Registry", + "NO_CHANGE": "Sem alterações para salvar.", "TOOLTIP": { - "SELF_REGISTRATION_ENABLE": "Habilitar registro.", - "SELF_REGISTRATION_DISABLE": "Desabilitar registro.", - "VERIFY_REMOTE_CERT": "Determina se a replicação da imagem deve verificar o certificado do Harbor remoto Harbor. Desmarque esse ítem quando o servidor remoto utilizar um certificado auto-assinado ou não confiável.", + "SELF_REGISTRATION_ENABLE": "Habilitar cadastro de usuários.", + "SELF_REGISTRATION_DISABLE": "Desabilitar cadastro de usuários.", + "VERIFY_REMOTE_CERT": "Determina se a replicação da imagem deve verificar o certificado do Harbor remoto. Desmarque se o servidor remoto utilizar um certificado auto-assinado ou não confiável.", "AUTH_MODE": "Por padrão, o modo de autenticação é via banco de dados, ex. As credenciais são armazenadas em um Banco de Dados local. Altere para LDAP se você deseja verificar as credenciais de um usuário utilizando um servidor LDAP.", "LDAP_SEARCH_DN": "A DN de um usuário que possui permissão para buscar no servidor LDAP/AD. Se o seu servidor LDAP/AD não suportar buscas anônimas, você deve configurar esse DN e ldap_search_pwd.", "LDAP_BASE_DN": "O DN base de onde deve ser buscado um usuário no LDAP/AD.", "LDAP_UID": "O atributo utilizado na busca de um uusário. Pode ser uid, cn, email, sAMAccountName ou outro atributo dependendo LDAP/AD.", "LDAP_SCOPE": "O escopo de busca de usuários.", "TOKEN_EXPIRATION": "O tempo de expiração (em minutos) de um token criado pelo serviço de token. O padrão é 30 minutos.", - "ROBOT_NAME_PREFIX": "Prefixed string for each robot name,and default value is 'robot$'", - "ROBOT_TOKEN_EXPIRATION": "O tempo de expiração (dias) do token da conta do robô, o padrão é 30 dias. Mostra o número de dias convertidos de minutos e arredonda para baixo", + "ROBOT_NAME_PREFIX": "Prefixo usado nas contas de automação. Se não informado, o valor usado é 'robot$'.", + "ROBOT_TOKEN_EXPIRATION": "O tempo de expiração (em dias) do token da conta do robô, o padrão é 30 dias. Mostra o número de dias convertidos de minutos e arredonda para baixo", "PRO_CREATION_RESTRICTION": "A opção para definir quais usuários possuem permissão de criar projetos. Por padrão, qualquer um pode criar projetos. Configure para 'Apenas Administradores' para que apenas Administradores possam criar projetos.", "ROOT_CERT_DOWNLOAD": "Baixar o certificado raiz do registry.", "SCANNING_POLICY": "Configura a política de análise das imagens baseado em diferentes requisitos. 'Nenhum': Nenhuma política ativa; 'Diariamente em': Dispara a análise diariamente no horário especificado.", "VERIFY_CERT": "Verificar o Certificado do Servidor LDAP", "REPO_TOOLTIP": "Usuários não podem efetuar qualquer operação nas imagens nesse modo.", - "WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed", - "HOURLY_CRON": "Run once an hour, beginning of hour. Equivalente a 0 0 * * * *.", - "WEEKLY_CRON": "Run once a week, midnight between Sat/Sun. Equivalente a 0 0 0 * * 0.", - "DAILY_CRON": "Run once a day, midnight. Equivalente a 0 0 0 * * *." + "WEBHOOK_TOOLTIP": "Habilite webhooks para enviar notificações a pontos de serviços remotos quando determinados eventos ocorrerem, como pull, push, análise de imagens, etc.", + "HOURLY_CRON": "Excutar toda hora, no início de cada hora. Equivalente a 0 0 * * * *.", + "WEEKLY_CRON": "Executar uma vez por semana, à meia noite de sábado para domingo. Equivalente a 0 0 0 * * 0.", + "DAILY_CRON": "Executar todos os dias à meia-noite. Equivalente a 0 0 0 * * *." }, "LDAP": { "URL": "URL LDAP", @@ -918,47 +918,47 @@ "LDAP_GROUP_ADMIN_DN": "DN de Grupo ADMIN no LDAP", "LDAP_GROUP_ADMIN_DN_INFO": "Especifica um DN de grupo LDAP. Todo usuário do LDAP nesse grupo terã privilégio de administrador. Mantenha em branco caso não deseje esse comportamento.", "LDAP_GROUP_MEMBERSHIP": "LDAP Group Membership", - "LDAP_GROUP_MEMBERSHIP_INFO": "The attribute indicates the membership of LDAP group, default value is memberof, in some LDAP server it could be \"ismemberof\"", + "LDAP_GROUP_MEMBERSHIP_INFO": "Atributo que informa a lista de grupos do usuário. Se não informado, o nome \"memberof\" será usado. Alguns servidores LDAP utilizam o atributo \"ismemberof\".", "GROUP_SCOPE": "Escopo de grupo LDAP", "GROUP_SCOPE_INFO": "O escopo que deve ser utilizado na busca por grupos, utiliza Subtree por padrão." }, "UAA": { - "ENDPOINT": "Endpoint UAA", + "ENDPOINT": "Endereço UAA", "CLIENT_ID": "ID de cliente UAA", - "CLIENT_SECRET": "Secret de Cliente UAA", + "CLIENT_SECRET": "Segredo do Cliente UAA", "VERIFY_CERT": "Verificar certificado UAA" }, "HTTP_AUTH": { - "ENDPOINT": "Server endpoint", - "TOKEN_REVIEW": "Ponto final do Token Review", - "SKIP_SEARCH": "Skip Search", - "VERIFY_CERT": "Verificar certificado de Authentication", - "ADMIN_GROUPS": "Admin Groups" + "ENDPOINT": "Endereço do servidor", + "TOKEN_REVIEW": "Endereço para Token Review", + "SKIP_SEARCH": "Pular Busca", + "VERIFY_CERT": "Verificar certificado SSL/HTTPS", + "ADMIN_GROUPS": "Grupos de Aministração" }, "OIDC": { - "OIDC_PROVIDER": "OIDC Fornecedor", - "OIDC_REDIREC_URL": "Please make sure the Redirect URI on the OIDC provider is set to", - "ENDPOINT": "OIDC Endpoint", - "CLIENT_ID": "ID de cliente OIDC", - "CLIENTSECRET": "OIDC Client Secret", - "SCOPE": "Escopo OIDC", - "OIDC_VERIFYCERT": "Verificar Certificado", - "OIDC_AUTOONBOARD": "Automatic onboarding", - "USER_CLAIM": "Username Claim", - "OIDC_SETNAME": "Definir o Utilizador OIDC", - "OIDC_SETNAMECONTENT": "Você deve Criar um Nome de usuário do Porto a primeira vez que autenticar através de um terceiro (OIDC). Isto será usado Dentro de Harbor para ser associado a projetos, papéis, etc.", - "OIDC_USERNAME": "Utilizador", - "GROUP_CLAIM_NAME": "Group Claim Name", - "OIDC_ADMIN_GROUP": "OIDC Admin Group", - "OIDC_ADMIN_GROUP_INFO": "Specify an OIDC admin group name. All OIDC users in this group will have harbor admin privilege. Keep it blank if you do not want to." + "OIDC_PROVIDER": "Provedor OIDC", + "OIDC_REDIREC_URL": "Confira na configuração do seu provedor que as URLs de Redirecionamento válidas incluem este endereço", + "ENDPOINT": "Endereço OIDC", + "CLIENT_ID": "ID do cliente OIDC", + "CLIENTSECRET": "Segredo do Cliente OIDC", + "SCOPE": "Escopo OIDC (scope)", + "OIDC_VERIFYCERT": "Verificar Certificado SSL/HTTPS", + "OIDC_AUTOONBOARD": "Cadastro automático de usuários", + "USER_CLAIM": "Atributo Username (claim)", + "OIDC_SETNAME": "Definir nome de usuário pelo OIDC", + "OIDC_SETNAMECONTENT": "Um nome de usuário deve ser criado na base local do Harbor para ser associado a projetos, perfís e permissões. Marque esta opção para que o nome de usuário seja obtido do provedor OIDC através do atributo (claim) informado.", + "OIDC_USERNAME": "Nome de usuário", + "GROUP_CLAIM_NAME": "Atributo com nome do grupo (claim)", + "OIDC_ADMIN_GROUP": "Grupo Administrativo", + "OIDC_ADMIN_GROUP_INFO": "Informe o nome do grupo OIDC que será considerado administrativo. Todos os usuários deste grupo obterão privilégios de adiministração no Harbor. Deixe vazio para ser ignorado." }, "SCANNING": { "TRIGGER_SCAN_ALL_SUCCESS": "Disparo de análise geral efetuado com sucesso!", "TRIGGER_SCAN_ALL_FAIL": "Falha ao disparar análise geral com erro: {{error}}", "TITLE": "Análise de vulnerabilidades", "SCAN_ALL": "Analisar todos", - "SCHEDULE_TO_SCAN_ALL": "Schedule to scan all", + "SCHEDULE_TO_SCAN_ALL": "Agendamento", "SCAN_NOW": "ANALISAR AGORA", "SCAN": "SCAN", "NONE_POLICY": "Nenhum", @@ -968,15 +968,15 @@ "DB_NOT_READY": "Banco de dados de vulnerabilidade pode não estar totalmente preparado!", "NEXT_SCAN": "Disponível após", "STATUS": { - "PENDING": "Pending", - "RUNNING": "Running", - "STOPPED": "Stopped", - "ERROR": "Error", - "SUCCESS": "Success", - "SCHEDULED": "Scheduled" + "PENDING": "Pendente", + "RUNNING": "Executando", + "STOPPED": "Parado", + "ERROR": "Erro", + "SUCCESS": "Successs", + "SCHEDULED": "Agendado" }, "MANUAL": "Manual", - "SCHEDULED": "Scheduled" + "SCHEDULED": "Agendado" }, "TEST_MAIL_SUCCESS": "Conexão ao servidor de Email foi verificada.", "TEST_LDAP_SUCCESS": "Conexão ao servidor de LDAP foi verificada.", @@ -984,7 +984,7 @@ "TEST_LDAP_FAILED": "Falha ao verificar servidor de LDAP com erro: {{param}}.", "LEAVING_CONFIRMATION_TITLE": "Confirme para sair", "LEAVING_CONFIRMATION_SUMMARY": "As alterações ainda não foram salvas. Você deseja sair da página atual?", - "TEST_OIDC_SUCCESS": "Connection to OIDC server is verified." + "TEST_OIDC_SUCCESS": "Conexão com o provedor OIDC verificada." }, "PAGE_NOT_FOUND": { "MAIN_TITLE": "Página não encontrada", @@ -994,7 +994,7 @@ "ABOUT": { "VERSION": "Versão", "BUILD": "Build", - "COPYRIGHT": "O Projeto Harbor é um projeto de Registry Cloud Native, Opensource e confiável que armazena, assina e analisa conteúdos. Harbor extende o projeto Opensource Docker Distribution adicionando funcionalidades como segurança, identidade e gerenciamento. Harbor suporta funcionalidades avançadas como gerenciamento de usuários, controle de acesso, monitoração de atividades e replicação entre instâncias. Ter um registry mais perto do ambiente de desenvolvimento e produção pode ainda melhorar a eficiência de transferência de imagem.", + "COPYRIGHT": "O Projeto Harbor é um projeto de Registry Cloud Native, Opensource e confiável que armazena, assina e analisa conteúdos. Harbor estende o projeto Opensource Docker Distribution adicionando funcionalidades como segurança, identidade e gerenciamento. Harbor suporta funcionalidades avançadas como gerenciamento de usuários, controle de acesso, auditoria de atividades e replicação entre instâncias. Ter um registry mais perto do ambiente de desenvolvimento e produção pode melhorar a eficiência de transferência de imagens.", "COPYRIGHT_SUFIX": ".", "TRADEMARK": "VMware is a registered trademark or trademark of VMware, Inc. in the United States and other jurisdictions. All other marks and names mentioned herein may be trademark of their respective companies.", "END_USER_LICENSE": "Contrato de Licença de Usuário Final", @@ -1013,7 +1013,7 @@ "INDEX_TOTAL": "TOTAL", "STORAGE": "ARMAZENAMENTO", "LIMIT": "Limite", - "STORAGE_USED": "Storage used" + "STORAGE_USED": "Armazenamento usado" }, "SEARCH": { "IN_PROGRESS": "Buscando...", @@ -1023,7 +1023,7 @@ "STATE": { "OTHER_STATUS": "Não analisado", "QUEUED": "Solicitado", - "ERROR": "Visualizar Log", + "ERROR": "Ver erros...", "SCANNING": "Analisando" }, "GRID": { @@ -1042,11 +1042,11 @@ }, "CHART": { "SCANNING_TIME": "Tempo de conclusão da análise:", - "SCANNING_PERCENT": "Scan completed percent:", - "SCANNING_PERCENT_EXPLAIN": "Scan completed percentage is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "SCANNING_PERCENT": "Progresso:", + "SCANNING_PERCENT_EXPLAIN": "O progresso é calculado como a razão do número de imagens analisadas pela quantidade total de imagens referenciadas o índice do repositório.", "TOOLTIPS_TITLE": "{{totalVulnerability}} de {{totalPackages}} {{package}} possuem {{vulnerability}}.", "TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} de {{totalPackages}} {{package}} possuem {{vulnerability}}.", - "TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability detected" + "TOOLTIPS_TITLE_ZERO": "Nenhuma vulnerabilidade encontrada" }, "SEVERITY": { "CRITICAL": "Crítico", @@ -1066,35 +1066,35 @@ "PACKAGES": "pacotes", "SCAN_NOW": "Analisar", "SCAN_BY": "SCAN BY {{scanner}}", - "REPORTED_BY": "Reported by {{scanner}}", + "REPORTED_BY": "Relatado por {{scanner}}", "NO_SCANNER": "NO SCANNER" }, "PUSH_IMAGE": { - "TITLE": "Push Command", + "TITLE": "Comando Push", "DOCKER": "Docker", "HELM": "Helm", "CNAB": "CNAB", - "TAG_COMMAND_CHART": "Tag a chart for this project:", - "PUSH_COMMAND_CHART": "Push a chart to this project:", - "PUSH_COMMAND_CNAB": "Push a CNAB to this project:", - "TOOLTIP": "Command references for pushing an artifact to this project.", - "TAG_COMMAND": "Taggeia uma imagem para esse projeto:", - "PUSH_COMMAND": "Envia uma imagem para esse projeto:", + "TAG_COMMAND_CHART": "Colocar tag em chart deste projeto:", + "PUSH_COMMAND_CHART": "Enviar um chart a este projeto:", + "PUSH_COMMAND_CNAB": "Enviar um CNAB a este projeto:", + "TOOLTIP": "Referência de comandos para enviar artefatos a este projeto.", + "TAG_COMMAND": "Colocar tag em uma imagem deste projeto:", + "PUSH_COMMAND": "Envia uma imagem a esse projeto:", "COPY_ERROR": "Copia falhou, por favor tente copiar o comando de referência manualmente." }, "ARTIFACT": { - "FILTER_FOR_ARTIFACTS": "Filter Artifacts", - "ADDITIONS": "Additions", - "ANNOTATION": "Annotations", - "OVERVIEW": "Overview", - "IMAGE": "IMAGE", + "FILTER_FOR_ARTIFACTS": "Filtrar Artefatos", + "ADDITIONS": "Adições", + "ANNOTATION": "Anotações", + "OVERVIEW": "Visão Geral", + "IMAGE": "IMAGEM", "CHART": "CHART", "CNAB": "CNAB", - "TAGGED": "Tagged", - "UNTAGGED": "Untagged", - "ALL": "All", - "PLACEHOLDER": "We couldn't find any artifacts!", - "SCAN_UNSUPPORTED": "Unsupported" + "TAGGED": "Com Tag", + "UNTAGGED": "Sem Tag", + "ALL": "Todos", + "PLACEHOLDER": "Nenhum artefato encontrado!", + "SCAN_UNSUPPORTED": "Não suportado" }, "TAG": { "CREATION_TIME_PREFIX": "Criado em", @@ -1104,7 +1104,7 @@ "DOCKER_VERSION": "Versão do Docker", "ARCHITECTURE": "Arquitetura", "OS": "SO", - "HAVE": "possui", + "HAVE": "possuem", "HAS": "possui", "SCAN_COMPLETION_TIME": "Análise completada", "IMAGE_VULNERABILITIES": "Vulnerabilidades da Imagem", @@ -1113,56 +1113,56 @@ "COPY_ERROR": "Cópia falhou, por favor tente copiar manualmente.", "FILTER_FOR_TAGS": "Filtrar Tags", "AUTHOR": "Autor", - "LABELS": "Labels", - "UPLOADTIME": "Upload Time", - "NAME": "Name", - "PULL_TIME": "Pull Time", - "PUSH_TIME": "Push Time", + "LABELS": "Marcadores", + "UPLOADTIME": "Horário de Envio", + "NAME": "Nome", + "PULL_TIME": "Horário do pull", + "PUSH_TIME": "Horário do push", "OF": "of", - "ITEMS": "items", - "ADD_TAG": "ADD TAG", - "REMOVE_TAG": "REMOVE TAG", - "NAME_ALREADY_EXISTS": "Tag already exists under the repository" + "ITEMS": "itens", + "ADD_TAG": "ADICIONAR TAG", + "REMOVE_TAG": "REMOVER TAG", + "NAME_ALREADY_EXISTS": "Tag já existe no repositório" }, "LABEL": { - "LABEL": "Label", + "LABEL": "Marcador", "DESCRIPTION": "Descrição", "CREATION_TIME": "Data de criação", - "NEW_LABEL": "Nova Label", + "NEW_LABEL": "Novo Marcador", "EDIT": "Editar", "DELETE": "Remover", - "LABEL_NAME": "Nome da Label", + "LABEL_NAME": "Nome do Marcador", "COLOR": "Cor", - "FILTER_LABEL_PLACEHOLDER": "Filtrar Labels", - "NO_LABELS": "Sem labels", - "DELETION_TITLE_TARGET": "Confirmar remoção de Label", + "FILTER_LABEL_PLACEHOLDER": "Filtrar Marcadores", + "NO_LABELS": "Sem marcadores", + "DELETION_TITLE_TARGET": "Confirmar remoção do marcador", "DELETION_SUMMARY_TARGET": "Você deseja remover {{param}}?", - "PLACEHOLDER": "Não foi possível encontrar nenhuma Label!", - "NAME_ALREADY_EXISTS": "Nome da Label já existe." + "PLACEHOLDER": "Não foi possível encontrar nenhum marcador!", + "NAME_ALREADY_EXISTS": "Marcador já existe." }, "QUOTA": { - "PROJECT": "Project", - "OWNER": "Owner", - "COUNT": "Count", - "STORAGE": "Storage", - "EDIT": "Edit", - "DELETE": "Delete", - "OF": "of", - "PROJECT_QUOTA_DEFAULT_ARTIFACT": "Default artifact count per project", - "PROJECT_QUOTA_DEFAULT_DISK": "Default disk space per project", - "EDIT_PROJECT_QUOTAS": "Edit Project Quotas", - "EDIT_DEFAULT_PROJECT_QUOTAS": "Edit Default Project Quotas", - "SET_QUOTAS": "Set the project quotas for project '{{params}}'", - "SET_DEFAULT_QUOTAS": "Set the default project quotas when creating new projects", - "COUNT_QUOTA": "Count quota", - "COUNT_DEFAULT_QUOTA": "Default count quota", - "STORAGE_QUOTA": "Storage quota", - "STORAGE_DEFAULT_QUOTA": "Default storage quota", - "SAVE_SUCCESS": "Quota edit success", - "UNLIMITED": "unlimited", - "INVALID_INPUT": "invalid input", - "PLACEHOLDER": "We couldn't find any project quotas", - "FILTER_PLACEHOLDER": "Search by name(exact match)" + "PROJECT": "Projeto", + "OWNER": "Dono", + "COUNT": "Total", + "STORAGE": "Armazenamento", + "EDIT": "Editar", + "DELETE": "Remover", + "OF": "de", + "PROJECT_QUOTA_DEFAULT_ARTIFACT": "Limite de artefatos por projeto", + "PROJECT_QUOTA_DEFAULT_DISK": "Limite de armazenamento por projeto", + "EDIT_PROJECT_QUOTAS": "Editar limites do projeto", + "EDIT_DEFAULT_PROJECT_QUOTAS": "Editar limites gerais para todos os projetos", + "SET_QUOTAS": "Definir limites para o projeto '{{params}}'", + "SET_DEFAULT_QUOTAS": "Definir os limites padronizados para novos projetos", + "COUNT_QUOTA": "Limite de itens", + "COUNT_DEFAULT_QUOTA": "Limite padrão de itens", + "STORAGE_QUOTA": "Limite de armazenamento", + "STORAGE_DEFAULT_QUOTA": "Limite padrão de armazenamento", + "SAVE_SUCCESS": "Limites definidos com sucesso", + "UNLIMITED": "ilimitado", + "INVALID_INPUT": "valor inválido", + "PLACEHOLDER": "Nenhuma limite encontrado nos projetos", + "FILTER_PLACEHOLDER": "Buscar pelo nome (exato)" }, "WEEKLY": { "MONDAY": "Segunda Feira", @@ -1182,7 +1182,7 @@ "DELETE_REPO": "Remover repositório", "DELETE_TAG": "Remover tag", "DELETE_USER": "Remover usuário", - "DELETE_ROBOT": "Delete robot", + "DELETE_ROBOT": "Remover conta de automação", "DELETE_REGISTRY": "Remover registry", "DELETE_REPLICATION": "Remover replicação", "DELETE_MEMBER": "Remover usuário membro", @@ -1233,460 +1233,460 @@ "CRON": "cron", "ON": "on", "AT": "at", - "NOSCHEDULE": "An error occurred in Get schedule" + "NOSCHEDULE": "Ocorreu um erro na rotina Get" }, "GC": { - "CURRENT_SCHEDULE": "Schedule to GC", + "CURRENT_SCHEDULE": "Agenda de Limpeza (GC)", "ON": "em", "AT": "em", - "GC_NOW": "GC AGORA", - "JOB_HISTORY": "GC History", + "GC_NOW": "COLETAR LIXO AGORA", + "JOB_HISTORY": "Histórico GC", "JOB_LIST": "Lista de tarefas de Limpeza", "JOB_ID": "ID DA TAREFA", "TRIGGER_TYPE": "TIPO DE DISPARO", "LATEST_JOBS": "Ultimas tarefas {{param}}", "LOG_DETAIL": "Detalhes de Log", - "MSG_SUCCESS": "Garbage Collection efetuado com sucesso", - "MSG_SCHEDULE_SET": "Agendamento de Garbage Collection efetuado", - "MSG_SCHEDULE_RESET": "Agendamento de Garbage Collection foi redefinido", - "PARAMETERS": "Parameters", - "DELETE_UNTAGGED": "Allow garbage collection on untagged artifacts", - "EXPLAIN": "GC is a compute intensive operation that may impact registry performance", - "DRY_RUN_SUCCESS": "Triggered dry run successfully" + "MSG_SUCCESS": "Limpeza efetuada com sucesso", + "MSG_SCHEDULE_SET": "Agendamento da limpeza efetuada", + "MSG_SCHEDULE_RESET": "Agendamento da limpeza redefinido", + "PARAMETERS": "Parâmetros", + "DELETE_UNTAGGED": "Permitir coleta de artefatos sem tags", + "EXPLAIN": "A limpeza exige recursos computacionais e pode impactar performance.", + "DRY_RUN_SUCCESS": "Teste executado com sucesso" }, "RETAG": { - "MSG_SUCCESS": "Copy artifact successfully", - "TIP_REPO": "A repository name is broken up into path components. A component of a repository name must be at least one lowercase, alpha-numeric characters, optionally separated by periods, dashes or underscores. More strictly, it must match the regular expression [a-z0-9]+(?:[._-][a-z0-9]+)*.If a repository name has two or more path components, they must be separated by a forward slash ('/').The total length of a repository name, including slashes, must be less the 256 characters.", - "TIP_TAG": "A tag is a label applied to a Docker image in a repository. Tags are how various images in a repository are distinguished from each other.It need to match Regex: (`[\\w][\\w.-]{0,127}`)" + "MSG_SUCCESS": "Artefato copiado com sucesso", + "TIP_REPO": "Nomes de repositórios são compostos por partes distintas. Cada parte deve conter apenas letras minúsculas, caracteres alfa-numéricos, pontos, traços (-) e traços-baixos (_). Via de regra, devem seguir a expressão regular [a-z0-9]+(?:[._-][a-z0-9]+)*. A serparação em níveis deve ser feita com barras (/) e o tamanho total deve ser menor que 256 caracteres.", + "TIP_TAG": "Tag é uma etiqueta colocada na imagem. Com elas, é possível diferenciar as várias versões da mesma imagem. Tags devm seguir a expressão regular: (`[\\w][\\w.-]{0,127}`)" }, "CVE_ALLOWLIST": { - "DEPLOYMENT_SECURITY": "Deployment security", - "CVE_ALLOWLIST": "CVE allowlist", - "SYS_ALLOWLIST_EXPLAIN": "System allowlist allows vulnerabilities in this list to be ignored when calculating the vulnerability of an image.", - "ADD_SYS": "Add CVE IDs to the system allowlist", - "WARNING_SYS": "The system CVE allowlist has expired. You can enable the allowlist by extending the expiration date.", - "WARNING_PRO": "The project CVE allowlist has expired. You can enable the allowlist by extending the expiration date.", - "ADD": "ADD", - "ENTER": "Enter CVE ID(s)", - "HELP": "Separator: commas or newline characters", - "NONE": "None", - "EXPIRES_AT": "Expires at", - "NEVER_EXPIRES": "Never expires", - "PRO_ALLOWLIST_EXPLAIN": "Project allowlist allows vulnerabilities in this list to be ignored in this project when pushing and pulling images.", - "PRO_OR_SYS": "You can either use the default allowlist configured at the system level or click on 'Project allowlist' to create a new allowlist", - "MERGE_INTO": "Add individual CVE IDs before clicking 'COPY FROM SYSTEM' to add system allowlist as well.", - "SYS_ALLOWLIST": "System allowlist", - "PRO_ALLOWLIST": "Project allowlist", - "ADD_SYSTEM": "COPY FROM SYSTEM" + "DEPLOYMENT_SECURITY": "Segurança de Implantação", + "CVE_ALLOWLIST": "CVEs permitidas", + "SYS_ALLOWLIST_EXPLAIN": "CVEs nesta lista serão ignoradas durante durante análises de vulnerabilidade", + "ADD_SYS": "Clique ADICIONAR para incluir CVEs na lista.", + "WARNING_SYS": "A lista de CVEs permitidas expirou. Você pode habilitar novamente definindo uma nova data de expiração.", + "WARNING_PRO": "A lista de CVEs permitidas do projeto expirou. Você pode habilitar novamente definindo uma nova data de expiração.", + "ADD": "ADICIONAR", + "ENTER": "Lista de CVEs", + "HELP": "Separador: vírgulas ou quebras de linha", + "NONE": "Nenhum", + "EXPIRES_AT": "Expira em", + "NEVER_EXPIRES": "Não expira", + "PRO_ALLOWLIST_EXPLAIN": "Inclua nesta lista vulnerabilidades que serão ignoradas apenas neste projeto.", + "PRO_OR_SYS": "Para não seguir as definições globais do sistema, selecione 'Definições deste projeto' para criar uma lista.", + "MERGE_INTO": "Adicione cada CVE individualmente antes de clicar em 'COPIAR DEFINIÇÕES GLOBAIS' para anexar.", + "SYS_ALLOWLIST": "Definições globais", + "PRO_ALLOWLIST": "Definições deste projeto", + "ADD_SYSTEM": "COPIAR DEFINIÇÕES GLOBAIS" }, "TAG_RETENTION": { - "TAG_RETENTION": "Tag Retention", - "RETENTION_RULES": "Retention rules", - "RULE_NAME_1": " the artifacts from the last {{number}} days", - "RULE_NAME_2": " the most recent active {{number}} artifacts", - "RULE_NAME_3": " the most recently pushed {{number}} artifacts", - "RULE_NAME_4": " the most recently pulled {{number}} artifacts", - "RULE_NAME_5": " always", - "ADD_RULE": "ADD RULE", - "ADD_RULE_HELP_1": "Click the ADD RULE button to add a rule.", - "ADD_RULE_HELP_2": "Tag retention polices run once a day.", - "RETENTION_RUNS": "Retention runs", - "RUN_NOW": "RUN NOW", - "WHAT_IF_RUN": "DRY RUN", - "ABORT": "ABORT", + "TAG_RETENTION": "Retenção de Tags", + "RETENTION_RULES": "Regras de retenção", + "RULE_NAME_1": " artefatos dos últimos {{number}} dias", + "RULE_NAME_2": " os {{number}} artifatos mais utilizados", + "RULE_NAME_3": " os {{number}} últimos artefatos enviados recentemente", + "RULE_NAME_4": " os {{number}} últimos artefatos baixados recentemente", + "RULE_NAME_5": " sempre", + "ADD_RULE": "ADICIONAR REGRA", + "ADD_RULE_HELP_1": "Clique ADICIONAR REGRA para criá-la.", + "ADD_RULE_HELP_2": "Políticas de retenção são avaliadas todos os dias.", + "RETENTION_RUNS": "Rotinas de retenção", + "RUN_NOW": "EXECUTAR AGORA", + "WHAT_IF_RUN": "TESTAR AGORA", + "ABORT": "INTERROMPER", "SERIAL": "ID", - "STATUS": "Status", - "DRY_RUN": "Dry Run", - "START_TIME": "Start Time", - "DURATION": "Duration", - "DETAILS": "Details", - "REPOSITORY": "Repository", - "EDIT": "Edit", - "DISABLE": "Disable", - "ENABLE": "Enable", - "DELETE": "Delete", - "ADD_TITLE": "Add Tag Retention Rule", - "ADD_SUBTITLE": "Specify a tag retention rule for this project. All tag retention rules are independently calculated and each rule can be applied to a selected list of repositories.", - "BY_WHAT": "By artifact count or number of days", - "RULE_TEMPLATE_1": "the artifacts from the last # days", - "RULE_TEMPLATE_2": "the most recent active # artifacts", - "RULE_TEMPLATE_3": "the most recently pushed # artifacts", - "RULE_TEMPLATE_4": "the most recently pulled # artifacts", - "RULE_TEMPLATE_5": "always", - "ACTION_RETAIN": " retain", - "UNIT_DAY": "DAYS", - "UNIT_COUNT": "COUNT", - "NUMBER": "NUMBER", - "IN_REPOSITORIES": "For the repositories", - "REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **", + "STATUS": "Situação", + "DRY_RUN": "Testar", + "START_TIME": "Início", + "DURATION": "Duração", + "DETAILS": "Detalhes", + "REPOSITORY": "Repositório", + "EDIT": "Editar", + "DISABLE": "Desabilitar", + "ENABLE": "Habilitar", + "DELETE": "Remover", + "ADD_TITLE": "Adicionar regra de retenção de tags", + "ADD_SUBTITLE": "Especifique os parâmetros de retenção deste projeto. Cada regra é avalidada de forma independente e pode ser aplicada a um conjunto restrito de repositórios.", + "BY_WHAT": "Número de artefatos ou dias", + "RULE_TEMPLATE_1": "somente artefatos dos últimos # dias", + "RULE_TEMPLATE_2": "somente os # artefatos mais usados recentemente", + "RULE_TEMPLATE_3": "somente os # últimos artefatos recebidos recentemente (pushed)", + "RULE_TEMPLATE_4": "somente os # últimos artefatos baixados recentemente (pulled)", + "RULE_TEMPLATE_5": "todos", + "ACTION_RETAIN": " reter", + "UNIT_DAY": "DIAS", + "UNIT_COUNT": "QUANTOS", + "NUMBER": "NÚMERO", + "IN_REPOSITORIES": "Repositórios", + "REP_SEPARATOR": "Lista de repositórios separados por vírgula: meurepo,repo* ou **", "TAGS": "Tags", - "INCLUDE_UNTAGGED": " untagged artifacts", - "UNTAGGED": " untagged", - "MATCHES_TAGS": "Matches tags", - "MATCHES_EXCEPT_TAGS": "Matches except tags", - "TAG_SEPARATOR": "Enter multiple comma separated tags, tag*, or **. Optionally include all untagged artifacts when applying the ‘including’ or ‘excluding’ selector by checking the box above.", - "LABELS": "Labels", - "MATCHES_LABELS": "Matches Labels", - "MATCHES_EXCEPT_LABELS": "Matches except Labels", - "REP_LABELS": "Enter multiple comma separated labels", - "RETENTION_RUN": "Retention Run", - "RETENTION_RUN_EXPLAIN": "Executing the retention policy can have adverse effects to the artifacts in this project and affected artifact tags will be deleted. Press CANCEL and use a DRY RUN to simulate the effect of this policy. Otherwise press RUN to proceed.", - "RETENTION_RUN_ABORTED": "Retention Run Aborted", - "RETENTION_RUN_ABORTED_EXPLAIN": "This retention run has been aborted. Artifacts already deleted are irreversible. You can initiate another run to continue to delete artifacts. In order to simulate a run, you can use the “DRY RUN”.", - "LOADING": "Loading...", - "NO_EXECUTION": "We couldn't find any executions!", - "NO_HISTORY": "We couldn't find any histories!", - "DELETION": "Deletions", - "EDIT_TITLE": "Edit Tag Retention Rule", + "INCLUDE_UNTAGGED": " artefatos sem tag", + "UNTAGGED": " sem tag", + "MATCHES_TAGS": "Tags que possuem", + "MATCHES_EXCEPT_TAGS": "Tags que não possuem", + "TAG_SEPARATOR": "Enter multiple comma separated tags, tag*, or **. Para incluir artefatos sem tag, marque a opção acima 'artefatos sem tag'", + "LABELS": "Marcadores", + "MATCHES_LABELS": "Incluir estes marcadores", + "MATCHES_EXCEPT_LABELS": "Não incluir estes marcadores", + "REP_LABELS": "Lista de marcadores seperados por vírgula", + "RETENTION_RUN": "Execuções", + "RETENTION_RUN_EXPLAIN": "A política de retenção terá efeito imediato nos artefatos deste projeto e as tags que se encaixam nas regras SERÃO REMOVIDAS. Você pode CANCELAR e usar o botão TESTAR para executar uma simulação. Caso tenha certeza, pode continuar clicando em EXECUTAR.", + "RETENTION_RUN_ABORTED": "Execução interrompida", + "RETENTION_RUN_ABORTED_EXPLAIN": "Execução foi interrompida. Entretanto, artefatos que já foram não podem ser recuperados. Para continuar, inicie uma nova execução. Para simular que seria removido use o botão TESTAR", + "LOADING": "Carregando...", + "NO_EXECUTION": "Nenhuma execução encontrada!", + "NO_HISTORY": "Nenhum histórico encontrado!", + "DELETION": "Removidos", + "EDIT_TITLE": "Editar regra de retenção de tag", "LOG": "Log", - "EXCLUDES": "Excludes", - "MATCHES": "Matches", - "REPO": " repositories", - "EXC": " excluding ", - "MAT": " matching ", - "AND": " and", - "WITH": " with ", - "WITHOUT": " without ", - "LOWER_LABELS": " labels", - "WITH_CONDITION": " with", + "EXCLUDES": "Exclusões", + "MATCHES": "Encontrados", + "REPO": " repositórios", + "EXC": " excluindo ", + "MAT": " incluindo ", + "AND": " e", + "WITH": " com ", + "WITHOUT": " sem ", + "LOWER_LABELS": " marcadores", + "WITH_CONDITION": " com", "LOWER_TAGS": " tags", - "TRIGGER": "Schedule", - "RETAINED": "Retained", + "TRIGGER": "Agenda de Disparo", + "RETAINED": "Retidos", "TOTAL": "Total", - "NONE": "none", - "RULE_NAME_6": " the artifacts pulled within the last {{number}} days", - "RULE_NAME_7": " the artifacts pushed within the last {{number}} days", - "RULE_TEMPLATE_6": " the artifacts pulled within the last # days", - "RULE_TEMPLATE_7": " the artifacts pushed within the last # days", - "SCHEDULE": "Schedule", - "SCHEDULE_WARNING": "Executing retention policy results in the irreversible effect of deleting artifacts from the Harbor project. Please double check all policies before scheduling.", - "EXISTING_RULE": "Existing rule", - "ILLEGAL_RULE": "Illegal rule", - "INVALID_RULE": "Invalid rule", - "COUNT_LARGE": "Parameter \"COUNT\" is too large", - "DAYS_LARGE": "Parameter \"DAYS\" is too large", - "EXECUTION_TYPE": "Execution Type", - "ACTION": "ACTION", - "YES": "Yes", - "NO": "No" + "NONE": "nenhum", + "RULE_NAME_6": " os artefatos baixados nos últimos {{number}} dias (pulled)", + "RULE_NAME_7": " os artefatos recebidos nos últimos {{number}} dias (pushed)", + "RULE_TEMPLATE_6": " os artefatos baixados nos últimos # dias (pulled)", + "RULE_TEMPLATE_7": " os artefatos recebidos nos últimos # dias (pushed)", + "SCHEDULE": "Agenda", + "SCHEDULE_WARNING": "A execução das políticas pode remover artefatos. Depois de removidos, não será possível recuperá-los. Confira as bem as regras antes de confirmar o agendamento.", + "EXISTING_RULE": "Regra existente", + "ILLEGAL_RULE": "Regra ilegal", + "INVALID_RULE": "Regra inválida", + "COUNT_LARGE": "O valor \"TOTAL\" é muito grande", + "DAYS_LARGE": "O valor \"DIAS\" é muito grande", + "EXECUTION_TYPE": "Tipo de Execução", + "ACTION": "AÇÃO", + "YES": "Sim", + "NO": "Não" }, "IMMUTABLE_TAG": { - "IMMUTABLE_RULES": "Immutability rules", - "ADD_RULE": "ADD RULE", - "ADD_RULE_HELP_1": "Click the ADD RULE button to add a rule.", - "EDIT": "Edit", - "DISABLE": "Disable", - "ENABLE": "Enable", - "DELETE": "Delete", - "ADD_TITLE": "Add Tag Immutability Rule", - "ADD_SUBTITLE": "Specify a tag immutability rule for this project. Note: all tag immutability rules are first independently calculated and then unioned to capture the final set of immutable tags.", - "IN_REPOSITORIES": "For the repositories", - "REP_SEPARATOR": "Enter multiple comma separated repos,repo*,or **", + "IMMUTABLE_RULES": "Regras de imutabilidade", + "ADD_RULE": "ADICIONAR REGRA", + "ADD_RULE_HELP_1": "Clique ADICIONAR REGRA para criar.", + "EDIT": "Editar", + "DISABLE": "Desabilitar", + "ENABLE": "Habilitar", + "DELETE": "Remover", + "ADD_TITLE": "Adicionar regra de imutabilidade das tags", + "ADD_SUBTITLE": "Informe as regras de imutabilidade para este projeto. Nota: as regras são calculadas de forma independente e, na sequẽncia, unificadas para determinar um conjunto final de tags imutáveis.", + "IN_REPOSITORIES": "Repositórios", + "REP_SEPARATOR": "Lista de repositórios: meurepo,repo* or **", "TAGS": "Tags", - "TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,or **", - "EDIT_TITLE": "Edit Tag Immutability Rule", - "EXC": " excluding ", - "MAT": " matching ", - "AND": " and", - "WITH": " with ", - "WITHOUT": " without ", - "LOWER_LABELS": " labels", + "TAG_SEPARATOR": "Lista de tags: minhatag,tag*,or **", + "EDIT_TITLE": "Editar Regra de Imutabilidade", + "EXC": " excluindo ", + "MAT": " incluindo ", + "AND": " e", + "WITH": " com ", + "WITHOUT": " sem ", + "LOWER_LABELS": " marcadores", "LOWER_TAGS": " tags", "NONE": " none", - "EXISTING_RULE": "Existing rule", - "ACTION": "ACTION" + "EXISTING_RULE": "Regra existente", + "ACTION": "AÇÃO" }, "SCANNER": { - "DELETION_SUMMARY": "Do you want to delete scanner {{param}}?", - "SKIP_CERT_VERIFY": "Check this box to skip certificate verification when the remote registry uses a self-signed or untrusted certificate.", - "NAME": "Name", - "NAME_EXISTS": "Name already exists", - "NAME_REQUIRED": "Name is required", - "NAME_REX": "Name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", - "DESCRIPTION": "Description", - "ENDPOINT": "Endpoint", - "ENDPOINT_EXISTS": "EndpointUrl already exists", - "ENDPOINT_REQUIRED": "EndpointUrl is required", - "ILLEGAL_ENDPOINT": "EndpointUrl is illegal", - "AUTH": "Authorization", - "NONE": "None", + "DELETION_SUMMARY": "Deseja remover o examinador {{param}}?", + "SKIP_CERT_VERIFY": "Marque para ignorar certificados inválidos ou auto-assinados no registry remoto.", + "NAME": "Nome", + "NAME_EXISTS": "Nome já existe", + "NAME_REQUIRED": "Nome é obrigatório", + "NAME_REX": "O nome precisa ter no mínimo 2 caracteres. Pode conter apenas letras minúsculas, numeros, os símbolos ._- e deve começar por uma letra ou número.", + "DESCRIPTION": "Descrição", + "ENDPOINT": "Endereço", + "ENDPOINT_EXISTS": "Endereço já usado por outro examinador", + "ENDPOINT_REQUIRED": "Endereço é obrigatório", + "ILLEGAL_ENDPOINT": "Endereço inválido", + "AUTH": "Autorização", + "NONE": "Nenhuma", "BASIC": "Basic", "BEARER": "Bearer", "API_KEY": "APIKey", - "USERNAME": "Username", - "USERNAME_REQUIRED": "Username is required", - "PASSWORD": "Password", - "PASSWORD_REQUIRED": "Password is required", + "USERNAME": "Nome de usuário", + "USERNAME_REQUIRED": "Nome de usuário é obrigatório", + "PASSWORD": "Senha", + "PASSWORD_REQUIRED": "Senha é obrigatória", "TOKEN": "Token", - "TOKEN_REQUIRED": "Token is required", - "API_KEY_REQUIRED": "APIKey is required", - "SKIP": "Skip certificate verification", - "ADD_SCANNER": "Add Scanner", - "EDIT_SCANNER": "Edit Scanner", - "TEST_CONNECTION": "TEST CONNECTION", - "ADD_SUCCESS": "Successfully added ", - "TEST_PASS": "Test passed", - "TEST_FAILED": "Ping: registration {{name}}:{{url}} is unreachable", - "UPDATE_SUCCESS": "Successfully updated", - "SCANNER_COLON": "Scanner:", - "NAME_COLON": "Name:", - "VENDOR_COLON": "Vendor:", - "VERSION_COLON": "Version:", - "CAPABILITIES": "Capabilities", - "CONSUMES_MIME_TYPES_COLON": "Consumes Mime Types:", - "PRODUCTS_MIME_TYPES_COLON": "Produces Mime Types:", - "PROPERTIES": "Properties", - "NEW_SCANNER": "NEW SCANNER", - "SET_AS_DEFAULT": "SET AS DEFAULT", - "HEALTH": "Health", - "DISABLED": "Disabled", - "NO_SCANNER": "Can not find any scanner", - "DEFAULT": "Default", - "HEALTHY": "Healthy", - "UNHEALTHY": "Unhealthy", - "SCANNERS": "Scanners", - "SCANNER": "Scanner", - "EDIT": "Edit", - "NOT_AVAILABLE": "Not Available", - "ADAPTER": "Adapter", - "VENDOR": "Vendor", - "VERSION": "Version", - "SELECT_SCANNER": "Select Scanner", - "ENABLED": "Enabled", - "ENABLE": "Enable", - "DISABLE": "Disable", - "DELETE_SUCCESS": "Successfully deleted", + "TOKEN_REQUIRED": "Token é obrigatório", + "API_KEY_REQUIRED": "APIKey é obrigatória", + "SKIP": "Não verificar certificado", + "ADD_SCANNER": "Adicionar examinador", + "EDIT_SCANNER": "Editar examinador", + "TEST_CONNECTION": "TESTAR CONEXÃO", + "ADD_SUCCESS": "Adicionado com sucesso ", + "TEST_PASS": "Testado com sucesso", + "TEST_FAILED": "Ping: endereço {{name}}:{{url}} indisponível", + "UPDATE_SUCCESS": "Atualizado com sucesso", + "SCANNER_COLON": "Examinador:", + "NAME_COLON": "Nome:", + "VENDOR_COLON": "Fornecedor:", + "VERSION_COLON": "Versão:", + "CAPABILITIES": "Habilidades", + "CONSUMES_MIME_TYPES_COLON": "Consome estes Mime Types:", + "PRODUCTS_MIME_TYPES_COLON": "Produz estes Mime Types:", + "PROPERTIES": "Propriedades", + "NEW_SCANNER": "NOVO EXAMINADOR", + "SET_AS_DEFAULT": "DEFINIR COMO PADRÃO", + "HEALTH": "Saúde", + "DISABLED": "Desabilitado", + "NO_SCANNER": "Nenhum examinador encontrado", + "DEFAULT": "Padrão", + "HEALTHY": "OK", + "UNHEALTHY": "PROBLEMAS", + "SCANNERS": "Examinadores", + "SCANNER": "Examinador", + "EDIT": "Editar", + "NOT_AVAILABLE": "Indisponível", + "ADAPTER": "Adaptador", + "VENDOR": "Fornecedor", + "VERSION": "Versão", + "SELECT_SCANNER": "Selecionar Examinador", + "ENABLED": "Habilitado", + "ENABLE": "Habilitar", + "DISABLE": "Desabilitado", + "DELETE_SUCCESS": "Removido com sucesso", "TOTAL": "Total", - "FIXABLE": "Fixable", - "DURATION": "Duration:", - "OPTIONS": "Options", - "USE_INNER": "Use internal registry address", - "USE_INNER_TIP": "If the option is checked, the scanner will be forced to use the internal registry address to access the related contents.", - "VULNERABILITY_SEVERITY": "Vulnerability severity:", - "CONFIRM_DELETION": "Confirm Scanner deletion", - "NO_PROJECT_SCANNER": "No Scanner", - "SET_UNHEALTHY_SCANNER": "Selected scanner:{{name}} is unhealthy", - "SCANNED_BY": "Scanned by:", - "IMAGE_SCANNERS": "Image Scanners", - "VIEW_DOC": "view documentation", - "ALL_SCANNERS": "All scanners", - "HELP_INFO_1": "The default scanner has been installed. To install other scanners refer to the ", - "HELP_INFO_2": "documentation.", - "NO_DEFAULT_SCANNER": "No default scanner" + "FIXABLE": "Corrigível", + "DURATION": "Duração:", + "OPTIONS": "Opções", + "USE_INNER": "Usar endreço interno do registry", + "USE_INNER_TIP": "Se marcado, o examinador será forçado a usar seu endereço de registry interno ao invés da URL pública.", + "VULNERABILITY_SEVERITY": "Severidade da vulnerabilidade:", + "CONFIRM_DELETION": "Confirmar remoção do examinador", + "NO_PROJECT_SCANNER": "Nenhum Examinador", + "SET_UNHEALTHY_SCANNER": "Examinador selecionado '{{name}}' está com problemas", + "SCANNED_BY": "Analisado por:", + "IMAGE_SCANNERS": "Examinadores de Imagens", + "VIEW_DOC": "Aprender mais a respeito", + "ALL_SCANNERS": "Todos os examinadores", + "HELP_INFO_1": "O examinador padrão foi instalado. Para incluir outros, confira a ", + "HELP_INFO_2": "documentação.", + "NO_DEFAULT_SCANNER": "Nenhum examinador padrão definido" }, "DISTRIBUTION": { - "FILTER_INSTANCE_PLACEHOLDER": "Filter instances", - "FILTER_HISTORIES_PLACEHOLDER": "Filter histories", - "ADD_ACTION": "NEW INSTANCE", - "PREHEAT_ACTION": "Preheat", - "EDIT_ACTION": "Edit", - "ENABLE_ACTION": "Enable", - "DISABLE_ACTION": "Disable", - "DELETE_ACTION": "Delete", - "NOT_FOUND": "We couldn't find any instance!", - "NAME": "Name", - "ENDPOINT": "Endpoint", - "STATUS": "Status", - "ENABLED": "Enable", - "SETUP_TIMESTAMP": "Setup Timestamp", - "PROVIDER": "Provider", - "DELETION_TITLE": "Confirm instance deletion", - "DELETION_SUMMARY": "Do you want to delete instance(s) {{param}}?", - "ENABLE_TITLE": "Enable instance(s)", - "ENABLE_SUMMARY": "Do you want to enable instance(s) {{param}}?", - "DISABLE_TITLE": "Disable instance(s)", - "DISABLE_SUMMARY": "Do you want to disable instance(s) {{param}}?", - "IMAGE": "Image", - "START_TIME": "Start time", - "FINISH_TIME": "Finish Time", - "INSTANCE": "Instance", - "HISTORIES": "Histories", - "CREATE_SUCCESS": "Instance created successfully", - "CREATE_FAILED": "Creating instance failed", - "DELETED_SUCCESS": "Instance(s) deleted successfully", - "DELETED_FAILED": "Deleting instance(s) failed", - "ENABLE_SUCCESS": "Instance(s) enabled successfully", - "ENABLE_FAILED": "Enabling instance(s) failed", - "DISABLE_SUCCESS": "Instance(s) disabled successfully", - "DISABLE_FAILED": "Disabling instance(s) failed", - "UPDATE_SUCCESS": "Instance updated successfully", - "UPDATE_FAILED": "Updating instance failed", - "REQUEST_PREHEAT_SUCCESS": "Preheat request successfully", - "REQUEST_PREHEAT_FAILED": "Preheat request failed", - "DESCRIPTION": "Description", - "AUTH_MODE": "Auth Mode", - "USERNAME": "Username", - "PASSWORD": "Password", + "FILTER_INSTANCE_PLACEHOLDER": "Filtrar instâncias", + "FILTER_HISTORIES_PLACEHOLDER": "Filtrar histórico", + "ADD_ACTION": "NOVA INSTÂNCIA", + "PREHEAT_ACTION": "Pré-análise", + "EDIT_ACTION": "Editar", + "ENABLE_ACTION": "Habilitar", + "DISABLE_ACTION": "Desabilitar", + "DELETE_ACTION": "Remover", + "NOT_FOUND": "Nenhuma instância encontrada!", + "NAME": "Nome", + "ENDPOINT": "Endereço", + "STATUS": "Situação", + "ENABLED": "Habilitar", + "SETUP_TIMESTAMP": "Horário de Configuração", + "PROVIDER": "Serviço", + "DELETION_TITLE": "Confirmar remoção da instância", + "DELETION_SUMMARY": "Deseja remover a(s) instância(s) {{param}}?", + "ENABLE_TITLE": "Instância(s) habilitada(s)", + "ENABLE_SUMMARY": "Deseja habilitar a(s) instância(s) {{param}}?", + "DISABLE_TITLE": "Desabilitar instância(s)", + "DISABLE_SUMMARY": "Deseja desabilitar a(s) instância(s) {{param}}?", + "IMAGE": "Imagem", + "START_TIME": "Início", + "FINISH_TIME": "Fim", + "INSTANCE": "Instância", + "HISTORIES": "Histórico", + "CREATE_SUCCESS": "Instância(s) criada(s) com sucesso", + "CREATE_FAILED": "Falha ao criar instância(s)", + "DELETED_SUCCESS": "Instância(s) removida(s) com sucesso", + "DELETED_FAILED": "Falha ao remover instância(s)", + "ENABLE_SUCCESS": "Instância(s) habilitada(s) com sucesso", + "ENABLE_FAILED": "Falha ao habilitar instância(s)", + "DISABLE_SUCCESS": "Instância(s) desabilitada(s) com sucesso", + "DISABLE_FAILED": "Falha ao desabilitar instância(s)", + "UPDATE_SUCCESS": "Instância atualizada com sucesso", + "UPDATE_FAILED": "Falha ao atualizar instância", + "REQUEST_PREHEAT_SUCCESS": "Solicitação de pré-análise feita com sucesso", + "REQUEST_PREHEAT_FAILED": "Falha na solicitação de pré-análise", + "DESCRIPTION": "Descrição", + "AUTH_MODE": "Modo de Autenticação", + "USERNAME": "Nome de usuário", + "PASSWORD": "Senha", "TOKEN": "Token", - "SETUP_NEW_INSTANCE": "Setup new instance", - "EDIT_INSTANCE": "Edit instance", + "SETUP_NEW_INSTANCE": "Configurar nova instância", + "EDIT_INSTANCE": "Editar instância", "SETUP": { - "NAME_PLACEHOLDER": "Input instance's name", - "DESCRIPTION_PLACEHOLDER": "Input instance's description", - "ENDPOINT_PLACEHOLDER": "Input instance's endpoint ", - "USERNAME_PLACEHOLDER": "Input username", - "PASSWORD_PLACEHOLDER": "Input password", - "TOKEN_PLACEHOLDER": "Input token" + "NAME_PLACEHOLDER": "Informe o nome da instância", + "DESCRIPTION_PLACEHOLDER": "Informa descrição da instância", + "ENDPOINT_PLACEHOLDER": "Informe o endereço da instância ", + "USERNAME_PLACEHOLDER": "Informe o nome de usuário", + "PASSWORD_PLACEHOLDER": "Informe a senha", + "TOKEN_PLACEHOLDER": "Informe o token" }, - "MAINTAINER": "Maintainer(s)", - "SOURCE": "Source", - "VERSION": "Version", - "SET_AS_DEFAULT": "Set as default", - "DELETE_INSTANCE": "Delete instance", - "ENABLE_INSTANCE": "Enable instance", - "DISABLE_INSTANCE": "Disable instance", - "SET_DEFAULT_SUCCESS": "Set as default successfully", - "SET_DEFAULT_FAILED": "Setting as default failed", - "UPDATE_INSTANCE": "Update instance", - "CREATE_INSTANCE": "Create instance" + "MAINTAINER": "Mantenedor(es)", + "SOURCE": "Fonte", + "VERSION": "Versão", + "SET_AS_DEFAULT": "Definir como padrão", + "DELETE_INSTANCE": "Remover instância", + "ENABLE_INSTANCE": "Habilitar instância", + "DISABLE_INSTANCE": "Desabilitar instância", + "SET_DEFAULT_SUCCESS": "Instância definida como padrão", + "SET_DEFAULT_FAILED": "Falha ao definir como padrão", + "UPDATE_INSTANCE": "Atualizar instância", + "CREATE_INSTANCE": "Criar instância" }, "P2P_PROVIDER": { - "P2P_PROVIDER": "P2P Preheat", - "POLICIES": "Policies", - "NEW_POLICY": "NEW POLICY", - "NAME": "Name", - "ENABLED": "Enabled", - "PROVIDER": "Provider", - "FILTERS": "Filters", - "TRIGGER": "Trigger", - "CREATED": "Creation Time", - "DESCRIPTION": "Description", - "NO_POLICY": "No policy", - "ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?", - "DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?", - "ENABLED_POLICY_TITLE": "Enable Policy", - "DISABLED_POLICY_TITLE": "Disable Policy", - "DELETE_POLICY_SUMMARY": "Do you want to delete policy {{names}}?", - "EDIT_POLICY": "Edit P2P Provider Policy", - "ADD_POLICY": "Create P2P Provider policy", - "NAME_REQUIRED": "Name is required", - "PROVIDER_REQUIRED": "Provider is required", - "ADDED_SUCCESS": "Added policy successfully", - "UPDATED_SUCCESS": "Updated policy successfully", - "EXECUTE": "EXECUTE", - "EXECUTE_SUCCESSFULLY": "Executed successfully", - "EXECUTE_TITLE": "Confirm Policy Execution", - "EXECUTE_SUMMARY": "Do you want to execute the policy {{param}}?", - "STOP_TITLE": "Confirm Execution Stopping", - "STOP_SUMMARY": "Do you want to stop executing the policy {{param}}?", - "STOP_SUCCESSFULLY": "Stopped execution successfully", - "STATUS_MSG": "Status Message", - "JOB_PLACEHOLDER": "We couldn't find any execution", - "PROVIDER_TYPE": "Vendor", - "ID": "Execution ID", - "NO_PROVIDER": "Please add a provider first", - "ARTIFACT": "Artifact", + "P2P_PROVIDER": "Pré-análise P2P", + "POLICIES": "Políticas", + "NEW_POLICY": "NOVA POLÍTICA", + "NAME": "Nome", + "ENABLED": "Habilitado", + "PROVIDER": "Provedor", + "FILTERS": "Filtros", + "TRIGGER": "Disparo", + "CREATED": "Criado em", + "DESCRIPTION": "Descrição", + "NO_POLICY": "Nenhuma política", + "ENABLED_POLICY_SUMMARY": "Gostaria de habilitar a política {{name}}?", + "DISABLED_POLICY_SUMMARY": "Gostaria de desabilitar a política {{name}}?", + "ENABLED_POLICY_TITLE": "Habilitar Política", + "DISABLED_POLICY_TITLE": "Desabilitar Política", + "DELETE_POLICY_SUMMARY": "Gostaria de remover a política {{names}}?", + "EDIT_POLICY": "Editar Provedor de Política P2P", + "ADD_POLICY": "Criar Provedor de Política P2P", + "NAME_REQUIRED": "Nome é obrigatório", + "PROVIDER_REQUIRED": "Provedor é obrigatório", + "ADDED_SUCCESS": "Política criada com sucesso", + "UPDATED_SUCCESS": "Política atualizada com sucesso", + "EXECUTE": "EXECUTAR", + "EXECUTE_SUCCESSFULLY": "Executado com sucesso", + "EXECUTE_TITLE": "Confirmar Execução da Política", + "EXECUTE_SUMMARY": "Gostaria de executar a política {{param}}?", + "STOP_TITLE": "Confirmar Interrupção da Política", + "STOP_SUMMARY": "Gostaria de interromper a execução da política {{param}}?", + "STOP_SUCCESSFULLY": "Política interrompida com sucesso", + "STATUS_MSG": "Situação", + "JOB_PLACEHOLDER": "Nenhuma execução encontrada", + "PROVIDER_TYPE": "Provedor", + "ID": "ID da Execução", + "NO_PROVIDER": "Por favor, adicione um provedor.", + "ARTIFACT": "Artefato", "DIGEST": "Digest", - "TYPE": "Type", - "TASKS": "Tasks", - "TASKS_PLACEHOLDER": "We couldn't find any task", - "SEVERITY_WARNING": "Vulnerability settings here conflicts with the relevant project configuration that will override the settings here", - "REPOS": "Repositories", + "TYPE": "Tipo", + "TASKS": "Tarfas", + "TASKS_PLACEHOLDER": "Nenhuma tarefa encontrada", + "SEVERITY_WARNING": "Conflito de configurações de vulnerabilidades. As configurações do projeto serão usadas.", + "REPOS": "Repositórios", "TAGS": "Tags", - "CRITERIA": "Criteria", - "ONLY_SIGNED": "Only signed images", - "PREHEAT_ON_PUSH": "Preheat on push", - "START_TEXT": "No vulnerability severity of", - "EDN_TEXT": "and above", - "LABELS": "Labels", - "SCHEDULE": "Schedule", - "TEST_FAILED": "Test failed", + "CRITERIA": "Critérios", + "ONLY_SIGNED": "Apenas imagens assinadas", + "PREHEAT_ON_PUSH": "Pré-análise no envio (push)", + "START_TEXT": "Nenhuma vulnerabilidade de severeidade", + "EDN_TEXT": "ou acima", + "LABELS": "Marcadores", + "SCHEDULE": "Agendamento", + "TEST_FAILED": "Falha no teste", "MANUAL": "Manual", - "SCHEDULED": "Scheduled", - "EVENT_BASED": "Event based", - "EVENT_BASED_EXPLAIN_LINE1": "The policy will be evaluated whenever the following events are occurred:", - "EVENT_BASED_EXPLAIN_LINE2": "Artifact is pushed", - "EVENT_BASED_EXPLAIN_LINE3": "Artifact is labeled", - "EVENT_BASED_EXPLAIN_LINE4": "Artifact is scanned", - "REPO_REQUIRED": "Repository is required", - "TAG_REQUIRED": "Tag is required", - "DELETE_SUCCESSFULLY": "Deleted policy successfully", - "UPDATED_SUCCESSFULLY": "Updated policy successfully", - "EXECUTIONS": "Executions", - "TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,or **", - "CONTENT_WARNING": "Content trust settings here conflicts with the relevant project configuration that will override the settings here", - "PREHEAT_EXPLAIN": "Preheat will migrate the image to the p2p network", - "CRITERIA_EXPLAIN": "As specified in 'Deployment security' section under Configuration tab", - "SKIP_CERT_VERIFY": "Check this box to skip certificate verification when the remote provider uses a self-signed or untrusted certificate.", - "NAME_TOOLTIP": "Policy name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", - "NEED_HELP": "Please ask your system admin to add a provider first" + "SCHEDULED": "Agendado", + "EVENT_BASED": "Baseado em evento", + "EVENT_BASED_EXPLAIN_LINE1": "A política será avaliada quando estes eventos ocorrerem:", + "EVENT_BASED_EXPLAIN_LINE2": "Artefato enviado (pushed)", + "EVENT_BASED_EXPLAIN_LINE3": "Artefato marcado (labeled)", + "EVENT_BASED_EXPLAIN_LINE4": "Artefato analisado.", + "REPO_REQUIRED": "Repositório é obrigatório", + "TAG_REQUIRED": "Tag é obrigatória", + "DELETE_SUCCESSFULLY": "Política removida com sucesso", + "UPDATED_SUCCESSFULLY": "Política atualizada com sucesso", + "EXECUTIONS": "Execuções", + "TAG_SEPARATOR": "Lista de tags separadas por vírgula: minhatag,tag* ou **", + "CONTENT_WARNING": "Conflito nas configurações de confiança de conteúdo. As configurações do projeto serão usadas.", + "PREHEAT_EXPLAIN": "Pré-análise torna suas imagens mais confiáveis perante a rede P2P", + "CRITERIA_EXPLAIN": "Conforme especificado na 'Segurança da implantação', na aba Configurações.", + "SKIP_CERT_VERIFY": "Marque para ignorar certificados inválidos ou auto-assinados.", + "NAME_TOOLTIP": "Nome da política deve ter no mínimo 2 caracteres. Pode conter ser composto de letras, números e dos símbolos ._- e deve começar com uma letra ou número.", + "NEED_HELP": "Solicite ao administrador do sistema a inclusão de um fornecedor." }, "PAGINATION": { - "PAGE_SIZE": "Page size" + "PAGE_SIZE": "Tamanho da Página" }, "SYSTEM_ROBOT": { - "READ": "Read", - "CREATE": "Create", - "ARTIFACT": "Artifact", + "READ": "Ler", + "CREATE": "Criar", + "ARTIFACT": "Artefato", "HELM": "Helm Chart", - "HELM_VERSION": "Helm Chart Version", - "ADD_ROBOT": "Add Robot", - "UPDATE_ROBOT": "Update Robot", - "UPDATE_ROBOT_SUCCESSFULLY": "Updated robot successfully", - "PLACEHOLDER": "Input new secret", - "SECRET": "Secret should be 8-20 characters long with at least 1 uppercase, 1 lowercase and 1 number.", - "REFRESH_SECRET": "Refresh Secret", - "REFRESH_SECRET_SUCCESS": "Refreshed secret successfully", - "DELETE_ROBOT": "Delete Robot", - "DELETE_ROBOT_SUCCESS": "Deleted robot(s) successfully", - "ENABLE_TITLE": "Enable Robot", - "ENABLE_SUMMARY": "Do you want to enable robot {{param}}?", - "DISABLE_TITLE": "Disable Robot", - "DISABLE_SUMMARY": "Do you want to disable robot {{param}}?", - "ENABLE_ROBOT_SUCCESSFULLY": "Enabled robot successfully", - "DISABLE_ROBOT_SUCCESSFULLY": "Disabled robot successfully", - "ROBOT_ACCOUNT": "Robot account", - "PROJECTS": "Projects", - "ALL_PROJECTS": "All projects with", - "PERMISSIONS": "PERMISSION(S)", - "REFRESH_SECRET_SUMMARY": "Refresh the robot account secret automatically, or optionally open the details to manually specify a new secret", - "TOKEN": "Secret", - "NEW_TOKEN": "New Secret", - "REFRESH": "REFRESH", - "PROJECTS_MODAL_TITLE": "Projects for Robot Account", - "PROJECTS_MODAL_SUMMARY": "There are the projects covered by this robot account.", - "CREATE_ROBOT": "Create System Robot Account", - "CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover specific projects. Choose \"Cover all projects\" to be applied to all existing and future projects", - "EDIT_ROBOT": "Edit System Robot Account", - "EDIT_ROBOT_SUMMARY": "Edit a system Robot Account. Choose \"Cover all projects\" to be applied to all exiting and future projects", - "EXPIRATION_TIME": "Expiration time", - "EXPIRATION_TIME_EXPLAIN": "The expiration time(in days and the starting point is creation time) of the token of the robot account. For being never expired, please enter \"-1\".", - "EXPIRATION_DEFAULT": "days(default)", - "EXPIRATION_DAYS": "Specify # of days", - "EXPIRATION_NEVER": "Never", - "EXPIRATION_REQUIRED": "Valid expiration time is required", - "COVER_ALL": "Cover all projects", - "COVER_ALL_EXPLAIN": "Check to be applied to all existing and future projects", - "COVER_ALL_SUMMARY": "All current and future projects selected.", - "RESET_PERMISSION": "RESET PERMISSIONS", - "PERMISSION_COLUMN": "Permissions", - "EXPIRES_AT": "Expires at", - "VIEW_SECRET": "REFRESH SECRET", - "LEGACY": "Legacy", - "CREATE_PROJECT_ROBOT": "Create Robot Account", - "CREATE_PROJECT_ROBOT_SUMMARY": "Create a robot account for this project", - "EDIT_PROJECT_ROBOT": "Edit Robot Account", - "EDIT_PROJECT_ROBOT_SUMMARY": "Edit a robot account for this project", - "NOT_FOUND": "We couldn't find any robots!", - "SELECT_ALL": "SELECT ALL", - "UNSELECT_ALL": "UNSELECT ALL", - "ROBOT_ACCOUNT_NAV": "Robot Accounts", - "COVERED_PROJECTS": "PROJECT(S)", - "CONFIRM_SECRET": "Confirm Secret", - "SECRET_AGAIN": "Input secret again", - "INCONSISTENT": "Two secrets are inconsistent", - "NAME_TOOLTIP": "Robot name should be 1~255 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", - "ENABLE_NEW_SECRET": "Enable this option to manually specify a new secret", - "DELETE": "Delete", - "ARTIFACT_LABEL": "Artifact label", - "SCAN": "Scan", + "HELM_VERSION": "Versão do Helm Chart", + "ADD_ROBOT": "Adicionar conta", + "UPDATE_ROBOT": "Atualizar conta", + "UPDATE_ROBOT_SUCCESSFULLY": "Conta atualizada com sucesso", + "PLACEHOLDER": "Informe um novo segredo", + "SECRET": "Segredos devem ter entre 8 e 20 caracteres, pelo menos 1 letra maiúscula, pelo menos 1 letra minúscula e pelo menos 1 número.", + "REFRESH_SECRET": "Redefinir Segredo", + "REFRESH_SECRET_SUCCESS": "Segredo redefinido com sucesso", + "DELETE_ROBOT": "Remover conta de automação", + "DELETE_ROBOT_SUCCESS": "Conta removida com sucesso", + "ENABLE_TITLE": "Habilitar conta de automação", + "ENABLE_SUMMARY": "Gostaria de habilitar a conta {{param}}?", + "DISABLE_TITLE": "Desabilitar conta de automação", + "DISABLE_SUMMARY": "Gostaria de desabilitar a conta de automação {{param}}?", + "ENABLE_ROBOT_SUCCESSFULLY": "Conta habilitada com sucesso", + "DISABLE_ROBOT_SUCCESSFULLY": "Conta desabilitada com sucesso", + "ROBOT_ACCOUNT": "Contas de Automação", + "PROJECTS": "Projetos", + "ALL_PROJECTS": "Todos os projetos com ", + "PERMISSIONS": "PERMISSÃO(ÕES)", + "REFRESH_SECRET_SUMMARY": "Redefinir o segredo da conta de automação automaticamente, ou informar um segredo manualmente", + "TOKEN": "Segredo", + "NEW_TOKEN": "Novo Segredo", + "REFRESH": "REDEFINIR", + "PROJECTS_MODAL_TITLE": "Projetos para contas de automação", + "PROJECTS_MODAL_SUMMARY": "Alguns projetos são usados por esta conta de automação.", + "CREATE_ROBOT": "Criar Conta de Automação", + "CREATE_ROBOT_SUMMARY": "Criando uma conta de sistema para automação de tarefas, com permissões específicas para alguns projetos. Ao selecionar \"Acesso Completo\", a conta terá acesso a todos os projetos atuais e também a projetos criados futuramente.", + "EDIT_ROBOT": "Editar Conta de Automação", + "EDIT_ROBOT_SUMMARY": "Editando conta de sistema para automação de tarefas, com permissões específicas para alguns projetos. Ao selecionar \"Acesso Completo\", a conta terá acesso a todos os projetos atuais e também a projetos criados futuramente.", + "EXPIRATION_TIME": "Validade", + "EXPIRATION_TIME_EXPLAIN": "Prazo de validade do acesso desta conta. Para não expirar, informe \"-1\".", + "EXPIRATION_DEFAULT": "dias(default)", + "EXPIRATION_DAYS": "Informe o # de dias", + "EXPIRATION_NEVER": "Nunca", + "EXPIRATION_REQUIRED": "Informe um prazo de validade corretamente", + "COVER_ALL": "Acesso Completo", + "COVER_ALL_EXPLAIN": "Marque para conceder acesso a todos os projetos, atuais e futuros.", + "COVER_ALL_SUMMARY": "Todos os projetos, atuais e futuros poderão ser acessados.", + "RESET_PERMISSION": "REDEFINIR PERMISSÕES", + "PERMISSION_COLUMN": "Permissões", + "EXPIRES_AT": "Deixará de funcionar em", + "VIEW_SECRET": "REDEFINIR SEGREDO", + "LEGACY": "Legado", + "CREATE_PROJECT_ROBOT": "Criar Conta de Automação", + "CREATE_PROJECT_ROBOT_SUMMARY": "Criar conta de automação para este projeto", + "EDIT_PROJECT_ROBOT": "Editar conta de automação", + "EDIT_PROJECT_ROBOT_SUMMARY": "Editar conta de automação para este projeto", + "NOT_FOUND": "Nenhuma conta de automação encontrada!", + "SELECT_ALL": "SELECIONAR TODAS", + "UNSELECT_ALL": "DESFAZER SELEÇÃO", + "ROBOT_ACCOUNT_NAV": "Contas de Automação", + "COVERED_PROJECTS": "PROJETO", + "CONFIRM_SECRET": "Confirmar Secret", + "SECRET_AGAIN": "Digite novamente", + "INCONSISTENT": "Confirmação do segredo não confere", + "NAME_TOOLTIP": "Nome da conta deve ter menos de 256 caracteres. Pode conter apenas letras minúsculas, números, os símbolos ._- e deve começar com uma letra ou número.", + "ENABLE_NEW_SECRET": "Informar o segredo manualmente", + "DELETE": "Remover", + "ARTIFACT_LABEL": "Marcador de artefato", + "SCAN": "Análise", "SCANNER_PULL": "Scanner Pull", - "SEARCH_BY_NAME": "Search by name(without prefix)", - "FINAL_PROJECT_NAME_TIP": "The final project robot name consists of the prefix,the project name,a plus mark and the current input value", - "FINAL_SYSTEM_NAME_TIP": "The final system robot name consists of the prefix and the current input value", + "SEARCH_BY_NAME": "Procurar por nome (sem prefixo)", + "FINAL_PROJECT_NAME_TIP": "Para evitar conflitos entre projetos, o nome completo será composto pelo nome do projeto concatenado a um marcador e o valor aqui informado.", + "FINAL_SYSTEM_NAME_TIP": "Este valor será concatenado ao prefixo do projeto.", "PUSH_AND_PULL": "Push", - "PUSH_PERMISSION_TOOLTIP": "Push permission can not work alone, it must work with pull permission" + "PUSH_PERMISSION_TOOLTIP": "Permissões de envio (push) presume também a permissão e recebimento (pull)." } } From ff617950b70f5a57ebcad259b2bb737e0bbc6a59 Mon Sep 17 00:00:00 2001 From: Pei-Tang Huang Date: Tue, 31 Aug 2021 15:43:30 +0800 Subject: [PATCH 013/135] Helm Chart should not be translated. (#15438) And the existing translation is also not accurate to the meaning of Helm. Signed-off-by: Pei-Tang Huang --- src/portal/src/i18n/lang/zh-tw-lang.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index d6fc4ef49e9..0c9602b17a6 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -213,7 +213,7 @@ "ROLE": "角色", "PUBLIC_OR_PRIVATE": "訪問級別", "REPO_COUNT": "鏡像倉庫數", - "CHART_COUNT":"頭盔圖表數", + "CHART_COUNT":"Helm Chart 數", "CREATION_TIME": "創建時間", "ACCESS_LEVEL": "訪問級別", "PUBLIC": "公開", @@ -263,7 +263,7 @@ "LABELS": "標籤", "PROJECTS": "項目", "CONFIG": "配置管理", - "HELMCHART":"頭盔圖表", + "HELMCHART":"Helm Chart", "ROBOT_ACCOUNTS": "機器人帳戶", "WEBHOOKS":"Webhooks", "IMMUTABLE_TAG": "不可變的Tag", @@ -353,7 +353,7 @@ "DELETE": "刪除", "CREAT_ROBOT_ACCOUNT": "創建機器人帳戶", "PERMISSIONS_IMAGE": "鏡像", - "PERMISSIONS_HELMCHART":"頭盔圖表", + "PERMISSIONS_HELMCHART":"Helm Chart", "PUSH": "推送", "PULL":"拉取", "FILTER_PLACEHOLDER": "過濾機器人帳戶", @@ -808,7 +808,7 @@ "SUMMARY":{ "QUOTAS": "容量", "PROJECT_REPOSITORY": "鏡像倉庫", - "PROJECT_HELM_CHART":"頭盔圖表", + "PROJECT_HELM_CHART":"Helm Chart", "PROJECT_MEMBER": "成員", "PROJECT_QUOTAS": "容量", "ARTIFACT_COUNT":"工件數量", From 581bb8833e73b4fe800cd8bb54024d89412ab328 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Wed, 1 Sep 2021 10:49:00 +0800 Subject: [PATCH 014/135] remove the internal legacy API to switch quota The init design of this API is to avoid the quota error leads to system disaster. As quota has been refineded and redis lock has been removed, the API can be deprecated safely. And this API is only call the DB to refresh quota data, user can call the SyncQuota API to handle this. Signed-off-by: Wang Yan --- src/core/api/harborapi_test.go | 1 - src/core/api/internal.go | 34 -------------------------------- src/core/api/internal_test.go | 36 ---------------------------------- src/server/route.go | 1 - 4 files changed, 72 deletions(-) diff --git a/src/core/api/harborapi_test.go b/src/core/api/harborapi_test.go index ed67e857739..ec9569b9dd2 100644 --- a/src/core/api/harborapi_test.go +++ b/src/core/api/harborapi_test.go @@ -115,7 +115,6 @@ func init() { beego.Router("/api/"+api.APIVersion+"/chartrepo/:repo/charts/:name/:version/labels", chartLabelAPIType, "get:GetLabels;post:MarkLabel") beego.Router("/api/"+api.APIVersion+"/chartrepo/:repo/charts/:name/:version/labels/:id([0-9]+)", chartLabelAPIType, "delete:RemoveLabel") - beego.Router("/api/internal/switchquota", &InternalAPI{}, "put:SwitchQuota") beego.Router("/api/internal/syncquota", &InternalAPI{}, "post:SyncQuota") // Init user Info diff --git a/src/core/api/internal.go b/src/core/api/internal.go index 6d6cd6518e2..12ab4c19ce7 100644 --- a/src/core/api/internal.go +++ b/src/core/api/internal.go @@ -69,40 +69,6 @@ func (ia *InternalAPI) RenameAdmin() { ia.DestroySession() } -// QuotaSwitcher ... -type QuotaSwitcher struct { - Enabled bool -} - -// SwitchQuota ... -func (ia *InternalAPI) SwitchQuota() { - var req QuotaSwitcher - if err := ia.DecodeJSONReq(&req); err != nil { - ia.SendBadRequestError(err) - return - } - ctx := orm.NewContext(ia.Ctx.Request.Context(), o.NewOrm()) - cur := config.ReadOnly(ctx) - // quota per project from disable to enable, it needs to update the quota usage bases on the DB records. - if !config.QuotaPerProjectEnable(ctx) && req.Enabled { - if !cur { - config.GetCfgManager(ctx).Set(ctx, common.ReadOnly, true) - config.GetCfgManager(ctx).Save(ctx) - } - if err := quota.RefreshForProjects(ctx); err != nil { - ia.SendInternalServerError(err) - return - } - } - defer func() { - ctx := orm.Context() - config.GetCfgManager(ctx).Set(ctx, common.ReadOnly, cur) - config.GetCfgManager(ctx).Set(ctx, common.QuotaPerProjectEnable, req.Enabled) - config.GetCfgManager(ctx).Save(ctx) - }() - return -} - // SyncQuota ... func (ia *InternalAPI) SyncQuota() { if !config.QuotaPerProjectEnable(orm.Context()) { diff --git a/src/core/api/internal_test.go b/src/core/api/internal_test.go index 02903a98bce..2ca101d21a4 100644 --- a/src/core/api/internal_test.go +++ b/src/core/api/internal_test.go @@ -19,42 +19,6 @@ import ( "testing" ) -// cannot verify the real scenario here -func TestSwitchQuota(t *testing.T) { - cases := []*codeCheckingCase{ - // 401 - { - request: &testingRequest{ - method: http.MethodPut, - url: "/api/internal/switchquota", - }, - code: http.StatusUnauthorized, - }, - // 200 - { - request: &testingRequest{ - method: http.MethodPut, - url: "/api/internal/switchquota", - credential: sysAdmin, - bodyJSON: &QuotaSwitcher{ - Enabled: true, - }, - }, - code: http.StatusOK, - }, - // 403 - { - request: &testingRequest{ - url: "/api/internal/switchquota", - method: http.MethodPut, - credential: nonSysAdmin, - }, - code: http.StatusForbidden, - }, - } - runCodeCheckingCases(t, cases...) -} - // cannot verify the real scenario here func TestSyncQuota(t *testing.T) { cases := []*codeCheckingCase{ diff --git a/src/server/route.go b/src/server/route.go index 5e73943d40d..7ee8c681763 100644 --- a/src/server/route.go +++ b/src/server/route.go @@ -46,7 +46,6 @@ func registerRoutes() { beego.Router(common.AuthProxyRediretPath, &controllers.AuthProxyController{}, "get:HandleRedirect") beego.Router("/api/internal/renameadmin", &api.InternalAPI{}, "post:RenameAdmin") - beego.Router("/api/internal/switchquota", &api.InternalAPI{}, "put:SwitchQuota") beego.Router("/api/internal/syncquota", &api.InternalAPI{}, "post:SyncQuota") beego.Router("/service/notifications/jobs/webhook/:id([0-9]+)", &jobs.Handler{}, "post:HandleNotificationJob") From 0585b148c73e185299609989354924e2ba1e494e Mon Sep 17 00:00:00 2001 From: Christopher Jenkins Date: Wed, 1 Sep 2021 15:28:30 -0700 Subject: [PATCH 015/135] Logrotate fails when cwd is not accessible Logrotate is run with sudo as the syslog user by cron.hourly The current working directory is `/root` which is inaccessible to the syslog user so the logrotate command fails. Currently the following stderr is being thrown away by the cron script: ``` error: cannot open current directory: Permission denied ``` Fixes #15468 Signed-off-by: Christopher Jenkins --- make/photon/log/logrotate | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/make/photon/log/logrotate b/make/photon/log/logrotate index 9042df92483..be1c97708a7 100755 --- a/make/photon/log/logrotate +++ b/make/photon/log/logrotate @@ -2,5 +2,6 @@ # run the logrotate with user 10000, the state file "/var/lib/logrotate/logrotate.status" # is specified to avoid the permission error +cd / sudo -u \#10000 -E /usr/sbin/logrotate -s /var/lib/logrotate/logrotate.status /etc/logrotate.conf -exit 0 \ No newline at end of file +exit 0 From 6b8c5c9edd0e85a24b0cd863c8950ad25ab6f79f Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Thu, 2 Sep 2021 09:04:33 +0800 Subject: [PATCH 016/135] Add usergroup search API (#15483) Fixes #15450 Add paging function to usergroup list/search API Fix some 500 error when adding LDAP user/group to project member Signed-off-by: stonezdj --- api/v2.0/swagger.yaml | 56 ++++++++++++++++++++++ src/controller/member/controller.go | 23 ++++++--- src/controller/usergroup/controller.go | 15 ++++-- src/core/auth/authenticator.go | 2 +- src/core/auth/ldap/ldap.go | 6 +-- src/pkg/usergroup/dao/dao.go | 52 +++++++++------------ src/pkg/usergroup/manager.go | 17 ++++--- src/pkg/usergroup/manager_test.go | 4 +- src/server/v2.0/handler/usergroup.go | 64 ++++++++++++++++++++++++-- 9 files changed, 181 insertions(+), 58 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index e67b967db12..7701a9f0c11 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -2697,6 +2697,8 @@ paths: - usergroup parameters: - $ref: '#/parameters/requestId' + - $ref: '#/parameters/page' + - $ref: '#/parameters/pageSize' - name: ldap_group_dn in: query type: string @@ -2709,6 +2711,13 @@ paths: type: array items: $ref: '#/definitions/UserGroup' + headers: + X-Total-Count: + description: The total count of available items + type: integer + Link: + description: Link to previous page and next page + type: string '401': $ref: '#/responses/401' '403': @@ -2744,6 +2753,41 @@ paths: $ref: '#/responses/409' '500': $ref: '#/responses/500' + /usergroups/search: + get: + summary: Search groups by groupname + description: | + This endpoint is to search groups by group name. It's open for all authenticated requests. + tags: + - usergroup + operationId: searchUserGroups + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/page' + - $ref: '#/parameters/pageSize' + - name: groupname + in: query + type: string + required: true + description: Group name for filtering results. + responses: + '200': + description: Search groups successfully. + schema: + type: array + items: + $ref: '#/definitions/UserGroupSearchItem' + headers: + X-Total-Count: + description: The total count of available items + type: integer + Link: + description: Link to previous page and next page + type: string + '401': + $ref: '#/responses/401' + '500': + $ref: '#/responses/500' '/usergroups/{group_id}': get: summary: Get user group information @@ -7689,6 +7733,18 @@ definitions: ldap_group_dn: type: string description: The DN of the LDAP group if group type is 1 (LDAP group). + UserGroupSearchItem: + type: object + properties: + id: + type: integer + description: The ID of the user group + group_name: + type: string + description: The name of the user group + group_type: + type: integer + description: 'The group type, 1 for LDAP group, 2 for HTTP group.' SupportedWebhookEventTypes: type: object description: Supportted webhook event types and notify types. diff --git a/src/controller/member/controller.go b/src/controller/member/controller.go index 80f1c07c434..ed51976bd74 100644 --- a/src/controller/member/controller.go +++ b/src/controller/member/controller.go @@ -26,7 +26,6 @@ import ( "github.com/goharbor/harbor/src/pkg/project" "github.com/goharbor/harbor/src/pkg/user" "github.com/goharbor/harbor/src/pkg/usergroup" - ugModel "github.com/goharbor/harbor/src/pkg/usergroup/model" ) // Controller defines the operation related to project member @@ -151,14 +150,26 @@ func (c *controller) Create(ctx context.Context, projectNameOrID interface{}, re member.EntityID = userID } else if len(req.MemberGroup.LdapGroupDN) > 0 { req.MemberGroup.GroupType = common.LDAPGroupType - // If groupname provided, use the provided groupname to name this group - groupID, err := auth.SearchAndOnBoardGroup(req.MemberGroup.LdapGroupDN, req.MemberGroup.GroupName) + // if the ldap group dn already exist + ugs, err := usergroup.Mgr.List(ctx, q.New(q.KeyWords{"LdapGroupDN": req.MemberGroup.LdapGroupDN, "GroupType": req.MemberGroup.GroupType})) if err != nil { return 0, err } - member.EntityID = groupID - } else if len(req.MemberGroup.GroupName) > 0 && req.MemberGroup.GroupType == common.HTTPGroupType || req.MemberGroup.GroupType == common.OIDCGroupType { - ugs, err := usergroup.Mgr.List(ctx, ugModel.UserGroup{GroupName: req.MemberGroup.GroupName, GroupType: req.MemberGroup.GroupType}) + if len(ugs) > 0 { + member.EntityID = ugs[0].ID + member.EntityType = common.GroupMember + } else { + // If groupname provided, use the provided groupname to name this group + groupID, err := auth.SearchAndOnBoardGroup(req.MemberGroup.LdapGroupDN, req.MemberGroup.GroupName) + if err != nil { + return 0, err + } + member.EntityID = groupID + } + + } else if len(req.MemberGroup.GroupName) > 0 { + // all group type can be added to project member by name + ugs, err := usergroup.Mgr.List(ctx, q.New(q.KeyWords{"GroupName": req.MemberGroup.GroupName, "GroupType": req.MemberGroup.GroupType})) if err != nil { return 0, err } diff --git a/src/controller/usergroup/controller.go b/src/controller/usergroup/controller.go index 6618b614c9a..5cf899d6c40 100644 --- a/src/controller/usergroup/controller.go +++ b/src/controller/usergroup/controller.go @@ -19,6 +19,7 @@ import ( "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/core/auth" "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/ldap" "github.com/goharbor/harbor/src/pkg/usergroup" "github.com/goharbor/harbor/src/pkg/usergroup/model" @@ -44,7 +45,9 @@ type Controller interface { // Populate populate user group and get the user group's id Populate(ctx context.Context, userGroups []model.UserGroup) ([]int, error) // List list user groups - List(ctx context.Context, userGroup model.UserGroup) ([]*model.UserGroup, error) + List(ctx context.Context, q *q.Query) ([]*model.UserGroup, error) + // Count user group count + Count(ctx context.Context, q *q.Query) (int64, error) } type controller struct { @@ -55,8 +58,8 @@ func newController() Controller { return &controller{mgr: usergroup.Mgr} } -func (c *controller) List(ctx context.Context, userGroup model.UserGroup) ([]*model.UserGroup, error) { - return c.mgr.List(ctx, userGroup) +func (c *controller) List(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) { + return c.mgr.List(ctx, query) } func (c *controller) Populate(ctx context.Context, userGroups []model.UserGroup) ([]int, error) { @@ -72,7 +75,7 @@ func (c *controller) Delete(ctx context.Context, id int) error { } func (c *controller) Update(ctx context.Context, id int, groupName string) error { - ug, err := c.mgr.List(ctx, model.UserGroup{ID: id}) + ug, err := c.mgr.List(ctx, q.New(q.KeyWords{"ID": id})) if err != nil { return err } @@ -109,3 +112,7 @@ func (c *controller) Create(ctx context.Context, group model.UserGroup) (int, er func (c *controller) Get(ctx context.Context, id int) (*model.UserGroup, error) { return c.mgr.Get(ctx, id) } + +func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) { + return c.mgr.Count(ctx, query) +} diff --git a/src/core/auth/authenticator.go b/src/core/auth/authenticator.go index 2120566d5aa..ed15a6d5ad1 100644 --- a/src/core/auth/authenticator.go +++ b/src/core/auth/authenticator.go @@ -224,7 +224,7 @@ func SearchAndOnBoardUser(username string) (int, error) { return 0, err } if user == nil { - return 0, ErrorUserNotExist + return 0, libErrors.NotFoundError(nil).WithMessage(fmt.Sprintf("user %s is not found", username)) } err = OnBoardUser(user) if err != nil { diff --git a/src/core/auth/ldap/ldap.go b/src/core/auth/ldap/ldap.go index 93bb60df21c..73c24ec7c23 100644 --- a/src/core/auth/ldap/ldap.go +++ b/src/core/auth/ldap/ldap.go @@ -194,7 +194,7 @@ func (l *Auth) SearchUser(username string) (*models.User, error) { log.Debugf("Found ldap user %v", user) } else { - return nil, fmt.Errorf("no user found, %v", username) + return nil, errors.NotFoundError(nil).WithMessage("no user found: %v", username) } return &user, nil @@ -224,7 +224,7 @@ func (l *Auth) SearchGroup(groupKey string) (*ugModel.UserGroup, error) { } if len(userGroupList) == 0 { - return nil, fmt.Errorf("failed to searh ldap group with groupDN:%v", groupKey) + return nil, errors.NotFoundError(nil).WithMessage("failed to searh ldap group with groupDN:%v", groupKey) } userGroup := ugModel.UserGroup{ GroupName: userGroupList[0].Name, @@ -244,7 +244,7 @@ func (l *Auth) OnBoardGroup(u *ugModel.UserGroup, altGroupName string) error { } u.GroupType = common.LDAPGroupType // Check duplicate LDAP DN in usergroup, if usergroup exist, return error - userGroupList, err := ugCtl.Ctl.List(ctx, ugModel.UserGroup{LdapGroupDN: u.LdapGroupDN}) + userGroupList, err := ugCtl.Ctl.List(ctx, q.New(q.KeyWords{"LdapGroupDN": u.LdapGroupDN})) if err != nil { return err } diff --git a/src/pkg/usergroup/dao/dao.go b/src/pkg/usergroup/dao/dao.go index d8918c884f6..a13fadf0b15 100644 --- a/src/pkg/usergroup/dao/dao.go +++ b/src/pkg/usergroup/dao/dao.go @@ -21,6 +21,7 @@ import ( "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" + "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/usergroup/model" "time" ) @@ -35,8 +36,10 @@ func init() { type DAO interface { // Add add user group Add(ctx context.Context, userGroup model.UserGroup) (int, error) + // Count query user group count + Count(ctx context.Context, query *q.Query) (int64, error) // Query query user group - Query(ctx context.Context, query model.UserGroup) ([]*model.UserGroup, error) + Query(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) // Get get user group by id Get(ctx context.Context, id int) (*model.UserGroup, error) // Delete delete user group by id @@ -60,7 +63,8 @@ var ErrGroupNameDup = errors.ConflictError(nil).WithMessage("duplicated user gro // Add - Add User Group func (d *dao) Add(ctx context.Context, userGroup model.UserGroup) (int, error) { - userGroupList, err := d.Query(ctx, model.UserGroup{GroupName: userGroup.GroupName, GroupType: common.HTTPGroupType}) + query := q.New(q.KeyWords{"GroupName": userGroup.GroupName, "GroupType": common.HTTPGroupType}) + userGroupList, err := d.Query(ctx, query) if err != nil { return 0, ErrGroupNameDup } @@ -84,43 +88,22 @@ func (d *dao) Add(ctx context.Context, userGroup model.UserGroup) (int, error) { } // Query - Query User Group -func (d *dao) Query(ctx context.Context, query model.UserGroup) ([]*model.UserGroup, error) { - o, err := orm.FromContext(ctx) +func (d *dao) Query(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) { + query = q.MustClone(query) + qs, err := orm.QuerySetter(ctx, &model.UserGroup{}, query) if err != nil { return nil, err } - sql := `select id, group_name, group_type, ldap_group_dn from user_group where 1=1 ` - sqlParam := make([]interface{}, 1) - var groups []*model.UserGroup - if len(query.GroupName) != 0 { - sql += ` and group_name = ? ` - sqlParam = append(sqlParam, query.GroupName) - } - - if query.GroupType != 0 { - sql += ` and group_type = ? ` - sqlParam = append(sqlParam, query.GroupType) - } - - if len(query.LdapGroupDN) != 0 { - sql += ` and ldap_group_dn = ? ` - sqlParam = append(sqlParam, utils.TrimLower(query.LdapGroupDN)) - } - if query.ID != 0 { - sql += ` and id = ? ` - sqlParam = append(sqlParam, query.ID) - } - _, err = o.Raw(sql, sqlParam).QueryRows(&groups) - if err != nil { + var usergroups []*model.UserGroup + if _, err := qs.All(&usergroups); err != nil { return nil, err } - return groups, nil + return usergroups, nil } // Get ... func (d *dao) Get(ctx context.Context, id int) (*model.UserGroup, error) { - userGroup := model.UserGroup{ID: id} - userGroupList, err := d.Query(ctx, userGroup) + userGroupList, err := d.Query(ctx, q.New(q.KeyWords{"ID": id})) if err != nil { return nil, err } @@ -192,3 +175,12 @@ func (d *dao) onBoardCommonUserGroup(ctx context.Context, g *model.UserGroup, ke return nil } + +func (d *dao) Count(ctx context.Context, query *q.Query) (int64, error) { + query = q.MustClone(query) + qs, err := orm.QuerySetterForCount(ctx, &model.UserGroup{}, query) + if err != nil { + return 0, err + } + return qs.Count() +} diff --git a/src/pkg/usergroup/manager.go b/src/pkg/usergroup/manager.go index 69cd6d83f33..cb54232778a 100644 --- a/src/pkg/usergroup/manager.go +++ b/src/pkg/usergroup/manager.go @@ -19,6 +19,7 @@ import ( "errors" "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common/utils" + "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/usergroup/dao" "github.com/goharbor/harbor/src/pkg/usergroup/model" ) @@ -35,7 +36,9 @@ type Manager interface { // Create create user group Create(ctx context.Context, userGroup model.UserGroup) (int, error) // List list user group - List(ctx context.Context, query model.UserGroup) ([]*model.UserGroup, error) + List(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) + // Count get user group count + Count(ctx context.Context, query *q.Query) (int64, error) // Get get user group by id Get(ctx context.Context, id int) (*model.UserGroup, error) // Populate populate user group from external auth server to Harbor and return the group id @@ -57,11 +60,7 @@ func newManager() Manager { } func (m *manager) Create(ctx context.Context, userGroup model.UserGroup) (int, error) { - query := model.UserGroup{ - GroupName: userGroup.GroupName, - GroupType: userGroup.GroupType, - } - ug, err := m.dao.Query(ctx, query) + ug, err := m.dao.Query(ctx, q.New(q.KeyWords{"GroupName": userGroup.GroupName, "GroupType": userGroup.GroupType})) if err != nil { return 0, err } @@ -71,7 +70,7 @@ func (m *manager) Create(ctx context.Context, userGroup model.UserGroup) (int, e return m.dao.Add(ctx, userGroup) } -func (m *manager) List(ctx context.Context, query model.UserGroup) ([]*model.UserGroup, error) { +func (m *manager) List(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) { return m.dao.Query(ctx, query) } @@ -127,3 +126,7 @@ func (m *manager) onBoardCommonUserGroup(ctx context.Context, g *model.UserGroup } return nil } + +func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) { + return m.dao.Count(ctx, query) +} diff --git a/src/pkg/usergroup/manager_test.go b/src/pkg/usergroup/manager_test.go index 7caf34cca2d..e318bb81358 100644 --- a/src/pkg/usergroup/manager_test.go +++ b/src/pkg/usergroup/manager_test.go @@ -15,6 +15,7 @@ package usergroup import ( + "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/usergroup/model" htesting "github.com/goharbor/harbor/src/testing" "github.com/stretchr/testify/suite" @@ -41,8 +42,7 @@ func (s *ManagerTestSuite) TestOnboardGroup() { } err := s.mgr.Onboard(ctx, ug) s.Nil(err) - qm := model.UserGroup{GroupType: 1, LdapGroupDN: "cn=harbor_dev,ou=groups,dc=example,dc=com"} - ugs, err := s.mgr.List(ctx, qm) + ugs, err := s.mgr.List(ctx, q.New(q.KeyWords{"GroupType": 1, "LdapGroupDN": "cn=harbor_dev,ou=groups,dc=example,dc=com"})) s.Nil(err) s.True(len(ugs) > 0) } diff --git a/src/server/v2.0/handler/usergroup.go b/src/server/v2.0/handler/usergroup.go index 7e3fc8dabcf..b5906ab0e7b 100644 --- a/src/server/v2.0/handler/usergroup.go +++ b/src/server/v2.0/handler/usergroup.go @@ -23,6 +23,7 @@ import ( ugCtl "github.com/goharbor/harbor/src/controller/usergroup" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/usergroup/model" "github.com/goharbor/harbor/src/server/v2.0/models" operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/usergroup" @@ -105,22 +106,35 @@ func (u *userGroupAPI) ListUserGroups(ctx context.Context, params operation.List if err != nil { return u.SendError(ctx, err) } - query := model.UserGroup{} + query, err := u.BuildQuery(ctx, nil, nil, params.Page, params.PageSize) + if err != nil { + return u.SendError(ctx, err) + } switch authMode { case common.LDAPAuth: - query.GroupType = common.LDAPGroupType + query.Keywords["GroupType"] = common.LDAPGroupType if params.LdapGroupDn != nil && len(*params.LdapGroupDn) > 0 { - query.LdapGroupDN = *params.LdapGroupDn + query.Keywords["LdapGroupDN"] = *params.LdapGroupDn } case common.HTTPAuth: - query.GroupType = common.HTTPGroupType + query.Keywords["GroupType"] = common.HTTPGroupType } + total, err := u.ctl.Count(ctx, query) + if err != nil { + return u.SendError(ctx, err) + } + if total == 0 { + return operation.NewListUserGroupsOK().WithXTotalCount(0).WithPayload([]*models.UserGroup{}) + } ug, err := u.ctl.List(ctx, query) if err != nil { return u.SendError(ctx, err) } - return operation.NewListUserGroupsOK().WithPayload(getUserGroupResp(ug)) + return operation.NewListUserGroupsOK(). + WithXTotalCount(total). + WithPayload(getUserGroupResp(ug)). + WithLink(u.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()) } func getUserGroupResp(ug []*model.UserGroup) []*models.UserGroup { result := make([]*models.UserGroup, 0) @@ -135,6 +149,18 @@ func getUserGroupResp(ug []*model.UserGroup) []*models.UserGroup { } return result } +func getUserGroupSearchItem(ug []*model.UserGroup) []*models.UserGroupSearchItem { + result := make([]*models.UserGroupSearchItem, 0) + for _, u := range ug { + ug := &models.UserGroupSearchItem{ + GroupName: u.GroupName, + GroupType: int64(u.GroupType), + ID: int64(u.ID), + } + result = append(result, ug) + } + return result +} func (u *userGroupAPI) UpdateUserGroup(ctx context.Context, params operation.UpdateUserGroupParams) middleware.Responder { if err := u.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceUserGroup); err != nil { return u.SendError(ctx, err) @@ -151,3 +177,31 @@ func (u *userGroupAPI) UpdateUserGroup(ctx context.Context, params operation.Upd } return operation.NewUpdateUserGroupOK() } + +func (u *userGroupAPI) SearchUserGroups(ctx context.Context, params operation.SearchUserGroupsParams) middleware.Responder { + if err := u.RequireAuthenticated(ctx); err != nil { + return u.SendError(ctx, err) + } + query, err := u.BuildQuery(ctx, nil, nil, params.Page, params.PageSize) + if err != nil { + return u.SendError(ctx, err) + } + if len(params.Groupname) == 0 { + return u.SendError(ctx, errors.BadRequestError(nil).WithMessage("need to provide groupname to search user group")) + } + query.Keywords["GroupName"] = &q.FuzzyMatchValue{Value: params.Groupname} + total, err := u.ctl.Count(ctx, query) + if err != nil { + return u.SendError(ctx, err) + } + if total == 0 { + return operation.NewSearchUserGroupsOK().WithXTotalCount(0).WithPayload([]*models.UserGroupSearchItem{}) + } + ug, err := u.ctl.List(ctx, query) + if err != nil { + return u.SendError(ctx, err) + } + return operation.NewSearchUserGroupsOK().WithXTotalCount(total). + WithPayload(getUserGroupSearchItem(ug)). + WithLink(u.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()) +} From 383635e97079b42ee3b9e24b481432e2380dd7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E4=B8=96=E5=86=9B?= <30999793+AllForNothing@users.noreply.github.com> Date: Thu, 2 Sep 2021 15:30:43 +0800 Subject: [PATCH 017/135] Refactor add group component (#15518) Signed-off-by: AllForNothing --- .../member/add-group/add-group.component.html | 125 ++----- .../member/add-group/add-group.component.scss | 40 +- .../add-group/add-group.component.spec.ts | 44 +-- .../member/add-group/add-group.component.ts | 343 +++++++++++------- .../add-http-auth-group.component.html | 37 -- .../add-http-auth-group.component.scss | 8 - .../add-http-auth-group.component.spec.ts | 50 --- .../add-http-auth-group.component.ts | 113 ------ .../add-member/add-member.component.html | 24 +- .../add-member/add-member.component.scss | 4 +- .../add-member/add-member.component.spec.ts | 6 +- .../member/add-member/add-member.component.ts | 202 ++++++----- .../base/project/member/member.component.html | 7 +- .../base/project/member/member.component.ts | 12 +- .../app/base/project/member/member.module.ts | 11 +- .../project/member/member.service.spec.ts | 18 - .../app/base/project/member/member.service.ts | 72 ---- .../src/app/base/project/member/member.ts | 40 -- .../target-exists-directive.spec.ts | 8 - .../directives/target-exists-directive.ts | 78 ---- .../app/shared/services/session.service.ts | 8 +- src/portal/src/i18n/lang/de-de-lang.json | 5 +- src/portal/src/i18n/lang/en-us-lang.json | 11 +- src/portal/src/i18n/lang/es-es-lang.json | 5 +- src/portal/src/i18n/lang/fr-fr-lang.json | 5 +- src/portal/src/i18n/lang/pt-br-lang.json | 5 +- src/portal/src/i18n/lang/tr-tr-lang.json | 5 +- src/portal/src/i18n/lang/zh-cn-lang.json | 19 +- src/portal/src/i18n/lang/zh-tw-lang.json | 5 +- 29 files changed, 450 insertions(+), 860 deletions(-) delete mode 100644 src/portal/src/app/base/project/member/add-http-auth-group/add-http-auth-group.component.html delete mode 100644 src/portal/src/app/base/project/member/add-http-auth-group/add-http-auth-group.component.scss delete mode 100644 src/portal/src/app/base/project/member/add-http-auth-group/add-http-auth-group.component.spec.ts delete mode 100644 src/portal/src/app/base/project/member/add-http-auth-group/add-http-auth-group.component.ts delete mode 100644 src/portal/src/app/base/project/member/member.service.spec.ts delete mode 100644 src/portal/src/app/base/project/member/member.service.ts delete mode 100644 src/portal/src/app/base/project/member/member.ts delete mode 100644 src/portal/src/app/shared/directives/target-exists-directive.spec.ts delete mode 100644 src/portal/src/app/shared/directives/target-exists-directive.ts diff --git a/src/portal/src/app/base/project/member/add-group/add-group.component.html b/src/portal/src/app/base/project/member/add-group/add-group.component.html index 83820f00251..5dced658a35 100644 --- a/src/portal/src/app/base/project/member/add-group/add-group.component.html +++ b/src/portal/src/app/base/project/member/add-group/add-group.component.html @@ -1,108 +1,43 @@ - - + +