diff --git a/make/common/templates/adminserver/env b/make/common/templates/adminserver/env index c74e300562..53b6244aa0 100644 --- a/make/common/templates/adminserver/env +++ b/make/common/templates/adminserver/env @@ -37,4 +37,5 @@ GODEBUG=netdns=cgo ADMIRAL_URL=$admiral_url WITH_NOTARY=$with_notary WITH_CLAIR=$with_clair +CLAIR_DB_PASSWORD=$pg_password RESET=false diff --git a/make/harbor.cfg b/make/harbor.cfg index f720ab76d2..93720ea09e 100644 --- a/make/harbor.cfg +++ b/make/harbor.cfg @@ -30,6 +30,10 @@ secretkey_path = /data #Admiral's url, comment this attribute, or set its value to NA when Harbor is standalone admiral_url = NA +#The password of the Clair's postgres database, only effective when Harbor is deployed with Clair. +#Please update it before deployment, subsequent update will cause Clair's API server and Harbor unable to access Clair's database. +clair_db_password = password + #NOTES: The properties between BEGIN INITIAL PROPERTIES and END INITIAL PROPERTIES #only take effect in the first boot, the subsequent changes of these properties #should be performed on web ui diff --git a/make/prepare b/make/prepare index 42f4af71c3..7cde721166 100755 --- a/make/prepare +++ b/make/prepare @@ -153,6 +153,7 @@ if rcp.has_option("configuration", "admiral_url"): admiral_url = rcp.get("configuration", "admiral_url") else: admiral_url = "" +pg_password = rcp.get("configuration", "clair_db_password") secret_key = get_secret_key(secretkey_path) ######## @@ -225,13 +226,15 @@ render(os.path.join(templates_dir, "adminserver", "env"), token_expiration=token_expiration, admiral_url=admiral_url, with_notary=args.notary_mode, - with_clair=args.clair_mode + with_clair=args.clair_mode, + pg_password=pg_password ) render(os.path.join(templates_dir, "ui", "env"), ui_conf_env, ui_secret=ui_secret, - jobservice_secret=jobservice_secret,) + jobservice_secret=jobservice_secret, + ) render(os.path.join(templates_dir, "registry", "config.yml"), @@ -370,11 +373,10 @@ if args.notary_mode: render(os.path.join(notary_temp_dir, "signer_env"), os.path.join(notary_config_dir, "signer_env"), alias = default_alias) if args.clair_mode: - pg_password = "password" clair_temp_dir = os.path.join(templates_dir, "clair") clair_config_dir = prep_conf_dir(config_dir, "clair") - print("Copying offline data file for clair DB") if os.path.exists(os.path.join(clair_config_dir, "postgresql-init.d")): + print("Copying offline data file for clair DB") shutil.rmtree(os.path.join(clair_config_dir, "postgresql-init.d")) shutil.copytree(os.path.join(clair_temp_dir, "postgresql-init.d"), os.path.join(clair_config_dir, "postgresql-init.d")) postgres_env = os.path.join(clair_config_dir, "postgres_env") diff --git a/src/adminserver/systemcfg/systemcfg.go b/src/adminserver/systemcfg/systemcfg.go index 07f3d0bdb7..64cfb3927f 100644 --- a/src/adminserver/systemcfg/systemcfg.go +++ b/src/adminserver/systemcfg/systemcfg.go @@ -45,6 +45,7 @@ var ( common.LDAPSearchPwd, common.MySQLPassword, common.AdminInitialPassword, + common.ClairDBPassword, } // all configurations need read from environment variables @@ -120,6 +121,7 @@ var ( env: "WITH_CLAIR", parse: parseStringToBool, }, + common.ClairDBPassword: "CLAIR_DB_PASSWORD", } // configurations need read from environment variables @@ -144,6 +146,7 @@ var ( env: "WITH_CLAIR", parse: parseStringToBool, }, + common.ClairDBPassword: "CLAIR_DB_PASSWORD", } ) diff --git a/src/common/const.go b/src/common/const.go index 51215cb305..78516f0038 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -66,6 +66,7 @@ const ( WithNotary = "with_notary" WithClair = "with_clair" ScanAllPolicy = "scan_all_policy" + ClairDBPassword = "clair_db_password" DefaultClairEndpoint = "http://clair:6060" ) diff --git a/src/common/dao/base.go b/src/common/dao/base.go index d2e456842b..86f107a1ef 100644 --- a/src/common/dao/base.go +++ b/src/common/dao/base.go @@ -43,13 +43,13 @@ type Database interface { } // InitClairDB ... -func InitClairDB() error { - //TODO: Read from env vars. +func InitClairDB(password string) error { + //Except for password other information will not be configurable, so keep it hard coded for 1.2.0. p := &pgsql{ host: "postgres", port: 5432, usr: "postgres", - pwd: "password", + pwd: password, database: "postgres", sslmode: false, } diff --git a/src/common/security/admiral/authcontext/authcontext.go b/src/common/security/admiral/authcontext/authcontext.go index 1180b13d68..ed567cacb9 100644 --- a/src/common/security/admiral/authcontext/authcontext.go +++ b/src/common/security/admiral/authcontext/authcontext.go @@ -170,23 +170,23 @@ func get(client *http.Client, url, token string, username ...string) (*AuthConte } // Login with credential and returns auth context and error -func Login(client *http.Client, url, username, password string) (*AuthContext, error) { +func Login(client *http.Client, url, username, password, token string) (*AuthContext, error) { data, err := json.Marshal(&struct { - Username string `json:"username"` Password string `json:"password"` }{ - Username: username, Password: password, }) if err != nil { return nil, err } - req, err := http.NewRequest(http.MethodPost, buildLoginURL(url), bytes.NewBuffer(data)) + req, err := http.NewRequest(http.MethodPost, buildLoginURL(url, username), bytes.NewBuffer(data)) if err != nil { return nil, err } + req.Header.Add(AuthTokenHeader, token) + return send(client, req) } @@ -228,7 +228,7 @@ func buildSpecificUserAuthCtxURL(url, principalID string) string { strings.TrimRight(url, "/"), principalID) } -// TODO update the url -func buildLoginURL(url string) string { - return strings.TrimRight(url, "/") + "/sso/login" +func buildLoginURL(url, principalID string) string { + return fmt.Sprintf("%s/auth/idm/principals/%s/security-context", + strings.TrimRight(url, "/"), principalID) } diff --git a/src/ui/config/config.go b/src/ui/config/config.go index 785b0efba3..6ef5f9f872 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -52,6 +52,8 @@ var ( // AdmiralClient is initialized only under integration deploy mode // and can be passed to project manager as a parameter AdmiralClient *http.Client + // TokenReader is used in integration mode to read token + TokenReader pms.TokenReader ) // Init configurations @@ -126,10 +128,11 @@ func initProjectManager() { path = defaultTokenFilePath } log.Infof("service token file path: %s", path) + TokenReader = &pms.FileTokenReader{ + Path: path, + } GlobalProjectMgr = pms.NewProjectManager(AdmiralClient, - AdmiralEndpoint(), &pms.FileTokenReader{ - Path: path, - }) + AdmiralEndpoint(), TokenReader) } // Load configurations @@ -358,12 +361,20 @@ func ClairEndpoint() string { return common.DefaultClairEndpoint } +// ClairDBPassword returns the password for accessing Clair's DB. +func ClairDBPassword() (string, error) { + cfg, err := mg.Get() + if err != nil { + return "", err + } + return cfg[common.ClairDBPassword].(string), nil +} + // AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string. func AdmiralEndpoint() string { cfg, err := mg.Get() if err != nil { log.Errorf("Failed to get configuration, will return empty string as admiral's endpoint, error: %v", err) - return "" } if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" { diff --git a/src/ui/filter/security.go b/src/ui/filter/security.go index 11bd281821..a84ab3bf55 100644 --- a/src/ui/filter/security.go +++ b/src/ui/filter/security.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "net/http" + "strings" beegoctx "github.com/astaxie/beego/context" "github.com/vmware/harbor/src/common/models" @@ -133,8 +134,13 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool { return false } + token, err := config.TokenReader.ReadToken() + if err != nil { + log.Errorf("failed to read solution user token: %v", err) + return false + } authCtx, err := authcontext.Login(config.AdmiralClient, - config.AdmiralEndpoint(), username, password) + config.AdmiralEndpoint(), username, password, token) if err != nil { log.Errorf("failed to authenticate %s: %v", username, err) return false @@ -172,7 +178,7 @@ func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool { } func filterReq(req *http.Request) bool { - path := req.URL.Path + path := strings.TrimRight(req.URL.Path, "/") if path == "/api/projects" && req.Method == http.MethodPost || path == "/service/token" && req.Method == http.MethodGet { return true diff --git a/src/ui/main.go b/src/ui/main.go index 110437fa46..719d1feac3 100644 --- a/src/ui/main.go +++ b/src/ui/main.go @@ -92,7 +92,11 @@ func main() { log.Fatalf("failed to initialize database: %v", err) } if config.WithClair() { - if err := dao.InitClairDB(); err != nil { + clairDBPassword, err := config.ClairDBPassword() + if err != nil { + log.Fatalf("failed to load clair database information: %v", err) + } + if err := dao.InitClairDB(clairDBPassword); err != nil { log.Fatalf("failed to initialize clair database: %v", err) } } diff --git a/src/ui/service/notifications/registry/handler.go b/src/ui/service/notifications/registry/handler.go index 2f60622bf6..54ed82b634 100644 --- a/src/ui/service/notifications/registry/handler.go +++ b/src/ui/service/notifications/registry/handler.go @@ -22,6 +22,7 @@ import ( "time" "github.com/vmware/harbor/src/common/dao" + clairdao "github.com/vmware/harbor/src/common/dao/clair" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils/log" @@ -105,8 +106,14 @@ func (n *NotificationHandler) Post() { }() go api.TriggerReplicationByRepository(pro.ProjectID, repository, []string{tag}, models.RepOpTransfer) + if autoScanEnabled(project) { - if err := uiutils.TriggerImageScan(repository, tag); err != nil { + last, err := clairdao.GetLastUpdate() + if err != nil { + log.Errorf("Failed to get last update from Clair DB, error: %v, the auto scan will be skipped.", err) + } else if last == 0 { + log.Infof("The Vulnerability data is not ready in Clair DB, the auto scan will be skipped.", err) + } else if err := uiutils.TriggerImageScan(repository, tag); err != nil { log.Warningf("Failed to scan image, repository: %s, tag: %s, error: %v", repository, tag, err) } } diff --git a/src/ui_ng/lib/src/endpoint/endpoint.component.html.ts b/src/ui_ng/lib/src/endpoint/endpoint.component.html.ts index 1a15723a1f..b09156dc87 100644 --- a/src/ui_ng/lib/src/endpoint/endpoint.component.html.ts +++ b/src/ui_ng/lib/src/endpoint/endpoint.component.html.ts @@ -31,7 +31,7 @@ export const ENDPOINT_TEMPLATE: string = ` {{t.creation_time | date: 'short'}} - {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}} + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}} {{pagination.totalItems}} {{'DESTINATION.ITEMS' | translate}} diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts index f19268eee5..60357b30f4 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts @@ -34,7 +34,7 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = ` - {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPLICATION.OF' | translate}} {{pagination.totalItems }} {{'REPLICATION.ITEMS' | translate}} + {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPLICATION.OF' | translate}} {{pagination.totalItems }} {{'REPLICATION.ITEMS' | translate}} diff --git a/src/ui_ng/lib/src/list-repository/list-repository.component.html.ts b/src/ui_ng/lib/src/list-repository/list-repository.component.html.ts index 158fac2202..f0b8146963 100644 --- a/src/ui_ng/lib/src/list-repository/list-repository.component.html.ts +++ b/src/ui_ng/lib/src/list-repository/list-repository.component.html.ts @@ -13,7 +13,7 @@ export const LIST_REPOSITORY_TEMPLATE = ` {{r.pull_count}} - {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} {{pagination.totalItems}}{{'REPOSITORY.ITEMS' | translate}} diff --git a/src/ui_ng/lib/src/replication/replication.component.html.ts b/src/ui_ng/lib/src/replication/replication.component.html.ts index 3bfb48634b..b8a0ebc4c7 100644 --- a/src/ui_ng/lib/src/replication/replication.component.html.ts +++ b/src/ui_ng/lib/src/replication/replication.component.html.ts @@ -67,7 +67,7 @@ export const REPLICATION_TEMPLATE: string = ` - {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}} + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}} {{pagination.totalItems}} {{'REPLICATION.ITEMS' | translate}} diff --git a/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.html.ts b/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.html.ts index ce315c0cfb..4be3af9c03 100644 --- a/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.html.ts +++ b/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.html.ts @@ -30,7 +30,7 @@ export const REPOSITORY_STACKVIEW_TEMPLATE: string = ` {{'CONFIG.SCANNING.DB_NOT_READY' | translate }} - {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} {{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}} diff --git a/src/ui_ng/lib/src/service/endpoint.service.ts b/src/ui_ng/lib/src/service/endpoint.service.ts index 1ef0a8babd..66d7973499 100644 --- a/src/ui_ng/lib/src/service/endpoint.service.ts +++ b/src/ui_ng/lib/src/service/endpoint.service.ts @@ -36,7 +36,7 @@ export abstract class EndpointService { * @abstract * @param {(number | string)} endpointId * @returns {(Observable | Endpoint)} - * + * * @memberOf EndpointService */ abstract getEndpoint(endpointId: number | string): Observable | Promise | Endpoint; @@ -185,14 +185,16 @@ export class EndpointDefaultService extends EndpointService { if(!endpoint) { return Promise.reject('Invalid endpoint.'); } - let requestUrl: string = `${this._endpointUrl}/ping`; + let requestUrl: string ; if(endpoint.id) { + requestUrl = `${this._endpointUrl}/${endpoint.id}/ping`; return this.http .post(requestUrl, {}) .toPromise() .then(response=>response.status) .catch(error=>Promise.reject(error)); } else { + requestUrl = `${this._endpointUrl}/ping`; return this.http .post(requestUrl, endpoint) .toPromise() diff --git a/src/ui_ng/lib/src/tag/tag.component.html.ts b/src/ui_ng/lib/src/tag/tag.component.html.ts index 014a04f93e..bd1d5b65c5 100644 --- a/src/ui_ng/lib/src/tag/tag.component.html.ts +++ b/src/ui_ng/lib/src/tag/tag.component.html.ts @@ -53,7 +53,7 @@ export const TAG_TEMPLATE = ` {{t.os}} - {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} {{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}     diff --git a/src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts b/src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts index d8459a8948..9bf12093b1 100644 --- a/src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts +++ b/src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts @@ -78,7 +78,8 @@ export const GRID_COMPONENT_HTML: string = ` - {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'VULNERABILITY.GRID.FOOT_OF' | translate}} {{pagination.totalItems}} {{'VULNERABILITY.GRID.FOOT_ITEMS' | translate}} + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'VULNERABILITY.GRID.FOOT_OF' | translate}} + {{pagination.totalItems}} {{'VULNERABILITY.GRID.FOOT_ITEMS' | translate}} diff --git a/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.html b/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.html index 74b36c4470..efe767bf42 100644 --- a/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.html +++ b/src/ui_ng/src/app/base/harbor-shell/harbor-shell.component.html @@ -17,7 +17,7 @@ diff --git a/src/ui_ng/src/app/harbor-routing.module.ts b/src/ui_ng/src/app/harbor-routing.module.ts index 75fd020ab5..750b2b6e2b 100644 --- a/src/ui_ng/src/app/harbor-routing.module.ts +++ b/src/ui_ng/src/app/harbor-routing.module.ts @@ -90,6 +90,10 @@ const harborRoutes: Routes = [ { path: 'endpoints', component: DestinationPageComponent + }, + { + path: '**', + redirectTo: 'endpoints' } ] }, diff --git a/src/ui_ng/src/app/log/audit-log.component.html b/src/ui_ng/src/app/log/audit-log.component.html index 60e9a7d8ef..c2d79ec6b3 100644 --- a/src/ui_ng/src/app/log/audit-log.component.html +++ b/src/ui_ng/src/app/log/audit-log.component.html @@ -56,8 +56,11 @@ {{l.op_time | date: 'short'}} - {{totalRecordCount}} {{'AUDIT_LOG.ITEMS' | translate}} - + {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'AUDIT_LOG.OF' | translate}} + {{pagination.totalItems }} {{'AUDIT_LOG.ITEMS' | translate}} + + diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.ts b/src/ui_ng/src/app/project/create-project/create-project.component.ts index 26155780c3..1a332bd81a 100644 --- a/src/ui_ng/src/app/project/create-project/create-project.component.ts +++ b/src/ui_ng/src/app/project/create-project/create-project.component.ts @@ -54,7 +54,7 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr createProjectOpened: boolean; hasChanged: boolean; - btnIsOk:boolean=false; + isSubmitOnGoing:boolean=false; staticBackdrop: boolean = true; closable: boolean = false; @@ -86,12 +86,10 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr this.isNameValid = cont.valid; if (this.isNameValid) { //Check exiting from backend - this.checkOnGoing = true; this.projectService .checkProjectExists(cont.value).toPromise() .then(() => { //Project existing - this.btnIsOk=true; this.isNameValid = false; this.nameTooltipText = 'PROJECT.NAME_ALREADY_EXISTS'; this.checkOnGoing = false; @@ -111,16 +109,24 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr } onSubmit() { - this.btnIsOk=false; + if (this.isSubmitOnGoing){ + return ; + } + + this.isSubmitOnGoing=true; this.projectService .createProject(this.project.name, this.project.public ? 1 : 0) .subscribe( status => { + this.isSubmitOnGoing=false; + this.create.emit(true); this.messageHandlerService.showSuccess('PROJECT.CREATED_SUCCESS'); this.createProjectOpened = false; }, error => { + this.isSubmitOnGoing=false; + let errorMessage: string; if (error instanceof Response) { switch (error.status) { @@ -187,7 +193,7 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr public get isValid(): boolean { return this.currentForm && this.currentForm.valid && - this.btnIsOk&& + !this.isSubmitOnGoing&& this.isNameValid && !this.checkOnGoing; } @@ -198,6 +204,7 @@ export class CreateProjectComponent implements AfterViewChecked, OnInit, OnDestr if (cont) { this.proNameChecker.next(cont.value); } + } } diff --git a/src/ui_ng/src/app/project/list-project/list-project.component.html b/src/ui_ng/src/app/project/list-project/list-project.component.html index 38f28c24a0..bc7c3c3cea 100644 --- a/src/ui_ng/src/app/project/list-project/list-project.component.html +++ b/src/ui_ng/src/app/project/list-project/list-project.component.html @@ -17,7 +17,8 @@ {{p.creation_time | date: 'short'}} - {{(projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}} - + {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} + {{pagination.totalItems }} {{'PROJECT.ITEMS' | translate}} + \ No newline at end of file diff --git a/src/ui_ng/src/app/project/member/member.component.html b/src/ui_ng/src/app/project/member/member.component.html index 5f5388d062..3b99fdb0c8 100644 --- a/src/ui_ng/src/app/project/member/member.component.html +++ b/src/ui_ng/src/app/project/member/member.component.html @@ -27,7 +27,11 @@ {{m.username}} {{roleInfo[m.role_id] | translate}} - {{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}} + + {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'MEMBER.OF' | translate}} + {{pagination.totalItems }} {{'MEMBER.ITEMS' | translate}} + + \ No newline at end of file diff --git a/src/ui_ng/src/app/shared/list-project-ro/list-project-ro.component.html b/src/ui_ng/src/app/shared/list-project-ro/list-project-ro.component.html index 51f59bfea0..c8df1a62ca 100644 --- a/src/ui_ng/src/app/shared/list-project-ro/list-project-ro.component.html +++ b/src/ui_ng/src/app/shared/list-project-ro/list-project-ro.component.html @@ -10,7 +10,10 @@ {{p.creation_time | date: 'short'}} - {{(projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}} - + {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} + {{pagination.totalItems }} {{'PROJECT.ITEMS' | translate}} + + \ No newline at end of file diff --git a/src/ui_ng/src/app/shared/list-repository-ro/list-repository-ro.component.html b/src/ui_ng/src/app/shared/list-repository-ro/list-repository-ro.component.html index e67f32c0bc..2871d8c517 100644 --- a/src/ui_ng/src/app/shared/list-repository-ro/list-repository-ro.component.html +++ b/src/ui_ng/src/app/shared/list-repository-ro/list-repository-ro.component.html @@ -8,7 +8,8 @@ {{r.pull_count}} - {{(repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}} - + {{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPOSITORY.OF' | translate}} + {{pagination.totalItems }} {{'REPOSITORY.ITEMS' | translate}} + \ No newline at end of file diff --git a/src/ui_ng/src/app/user/user.component.html b/src/ui_ng/src/app/user/user.component.html index 947a712c32..85fb85292f 100644 --- a/src/ui_ng/src/app/user/user.component.html +++ b/src/ui_ng/src/app/user/user.component.html @@ -27,8 +27,10 @@ {{user.email}} {{user.creation_time | date: 'short'}} - {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{pagination.totalItems}} users - {{'USER.ITEMS' | translate}} + + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'USER.OF' | translate }} + {{pagination.totalItems}} {{'USER.ITEMS' | translate }} + diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index 6501446e9b..31824de7ed 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -117,7 +117,7 @@ "DELETION_TITLE": "Confirm user deletion", "DELETION_SUMMARY": "Do you want to delete user {{param}}?", "DELETE_SUCCESS": "User deleted successfully.", - "ITEMS": "item(s)" + "ITEMS": "items" }, "PROJECT": { "PROJECTS": "Projects", @@ -143,7 +143,7 @@ "NAME_ALREADY_EXISTS": "Project name already exists.", "NAME_IS_ILLEGAL": "Project name is invalid.", "UNKNOWN_ERROR": "An unknown error occurred while creating the project.", - "ITEMS": "item(s)", + "ITEMS": "items", "DELETION_TITLE": "Confirm project deletion", "DELETION_SUMMARY": "Do you want to delete project {{param}}?", "FILTER_PLACEHOLDER": "Filter Projects", @@ -171,7 +171,7 @@ "DEVELOPER": "Developer", "GUEST": "Guest", "DELETE": "Delete", - "ITEMS": "item(s)", + "ITEMS": "items", "ACTIONS": "Actions", "USERNAME_IS_REQUIRED": "Username is required", "USERNAME_DOES_NOT_EXISTS": "Username does not exist.", @@ -199,7 +199,7 @@ "OTHERS": "Others", "ADVANCED": "Advanced", "SIMPLE": "Simple", - "ITEMS": "item(s)", + "ITEMS": "items", "FILTER_PLACEHOLDER": "Filter Logs", "INVALID_DATE": "Invalid date." }, @@ -257,7 +257,7 @@ "END_TIME": "End Time", "LOGS": "Logs", "OF": "of", - "ITEMS": "item(s)", + "ITEMS": "items", "TOGGLE_ENABLE_TITLE": "Enable Rule", "CONFIRM_TOGGLE_ENABLE_POLICY": "After enabling the replication rule, all repositories under the project will be replicated to the destination registry. \nPlease confirm to continue.", "TOGGLE_DISABLE_TITLE": "Disable Rule", @@ -296,7 +296,7 @@ "FAILED_TO_GET_TARGET": "Failed to get endpoint.", "CREATION_TIME": "Creation Time", "OF": "of", - "ITEMS": "item(s)", + "ITEMS": "items", "CREATED_SUCCESS": "Created endpoint successfully.", "UPDATED_SUCCESS": "Updated endpoint successfully.", "DELETED_SUCCESS": "Deleted endpoint successfully.", @@ -331,7 +331,7 @@ "SHOW_DETAILS": "Show Details", "REPOSITORIES": "Repositories", "OF": "of", - "ITEMS": "item(s)", + "ITEMS": "items", "POP_REPOS": "Popular Repositories", "DELETED_REPO_SUCCESS": "Deleted repository successfully.", "DELETED_TAG_SUCCESS": "Deleted tag successfully.", diff --git a/src/ui_ng/src/i18n/lang/es-es-lang.json b/src/ui_ng/src/i18n/lang/es-es-lang.json index 2fb260b599..80b68196e6 100644 --- a/src/ui_ng/src/i18n/lang/es-es-lang.json +++ b/src/ui_ng/src/i18n/lang/es-es-lang.json @@ -117,7 +117,7 @@ "DELETION_TITLE": "Confirmar eliminación de usuario", "DELETION_SUMMARY": "¿Quiere eliminar el usuario {{param}}?", "DELETE_SUCCESS": "Usuario eliminado satisfactoriamente.", - "ITEMS": "elemento(s)" + "ITEMS": "elementos" }, "PROJECT": { "PROJECTS": "Proyectos", @@ -143,7 +143,7 @@ "NAME_ALREADY_EXISTS": "Ya existe un proyecto con ese nombre.", "NAME_IS_ILLEGAL": "El nombre del proyecto no es valido.", "UNKNOWN_ERROR": "Ha ocurrido un error al crear el proyecto.", - "ITEMS": "elemento(s)", + "ITEMS": "elementos", "DELETION_TITLE": "Confirmar eliminación del proyecto", "DELETION_SUMMARY": "¿Quiere eliminar el proyecto {{param}}?", "FILTER_PLACEHOLDER": "Filtrar proyectos", @@ -171,7 +171,7 @@ "DEVELOPER": "Desarrollador", "GUEST": "Invitado", "DELETE": "Eliminar", - "ITEMS": "elemento(s)", + "ITEMS": "elementos", "ACTIONS": "Acciones", "USERNAME_IS_REQUIRED": "El nombre de usuario es obligatorio", "USERNAME_DOES_NOT_EXISTS": "Ese nombre de usuario no existe.", @@ -199,7 +199,7 @@ "OTHERS": "Otros", "ADVANCED": "Avanzado", "SIMPLE": "Simple", - "ITEMS": "elemento(s)", + "ITEMS": "elementos", "FILTER_PLACEHOLDER": "Filtrar logs", "INVALID_DATE": "Fecha invalida." }, @@ -257,7 +257,7 @@ "END_TIME": "Fecha de Finalización", "LOGS": "Logs", "OF": "of", - "ITEMS": "elemento(s)", + "ITEMS": "elementos", "TOGGLE_ENABLE_TITLE": "Activar Regla", "CONFIRM_TOGGLE_ENABLE_POLICY": "Después de la activación de esta regla, todos los repositorios de este proyecto serán replicados al registro de destino.\nPor favor, confirme para continuar.", "TOGGLE_DISABLE_TITLE": "Desactivar Regla", @@ -296,7 +296,7 @@ "FAILED_TO_GET_TARGET": "Fallo al obtener el endpoint.", "CREATION_TIME": "Fecha de creación", "OF": "of", - "ITEMS": "elemento(s)", + "ITEMS": "elementos", "CREATED_SUCCESS": "Endpoint creado satisfactoriamente.", "UPDATED_SUCCESS": "Endpoint actualizado satisfactoriamente.", "DELETED_SUCCESS": "Endpoint eliminado satisfactoriamente.", @@ -332,7 +332,7 @@ "SHOW_DETAILS": "Mostrar Detalles", "REPOSITORIES": "Repositorios", "OF": "of", - "ITEMS": "elemento(s)", + "ITEMS": "elementos", "POP_REPOS": "Repositorios Populares", "DELETED_REPO_SUCCESS": "Repositorio eliminado satisfactoriamente.", "DELETED_TAG_SUCCESS": "Etiqueta eliminada satisfactoriamente.", diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index 1f466442b8..a99ff7abf1 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -117,6 +117,7 @@ "DELETION_TITLE": "删除用户确认", "DELETION_SUMMARY": "你确认删除用户 {{param}}?", "DELETE_SUCCESS": "成功删除用户。", + "OF": "共计", "ITEMS": "条记录" }, "PROJECT": { @@ -143,6 +144,7 @@ "NAME_ALREADY_EXISTS": "项目名称已存在。", "NAME_IS_ILLEGAL": "项目名称非法。", "UNKNOWN_ERROR": "创建项目时发生未知错误。", + "OF": "共计", "ITEMS": "条记录", "DELETION_TITLE": "删除项目确认", "DELETION_SUMMARY": "你确认删除项目 {{param}}?", @@ -171,6 +173,7 @@ "DEVELOPER": "开发人员", "GUEST": "访客", "DELETE": "删除", + "OF": "共计", "ITEMS": "条记录", "ACTIONS": "操作", "USERNAME_IS_REQUIRED": "用户名为必填项。", @@ -199,6 +202,7 @@ "OTHERS": "其他", "ADVANCED": "高级检索", "SIMPLE": "简单检索", + "OF": "共计", "ITEMS": "条记录", "FILTER_PLACEHOLDER": "过滤日志", "INVALID_DATE": "无效日期。"