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": "无效日期。"