Merge branch 'master' into fix_issue_#2793

This commit is contained in:
Steven Zou 2017-07-19 12:34:43 +08:00
commit 5c876621ec
31 changed files with 133 additions and 62 deletions

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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",
}
)

View File

@ -66,6 +66,7 @@ const (
WithNotary = "with_notary"
WithClair = "with_clair"
ScanAllPolicy = "scan_all_policy"
ClairDBPassword = "clair_db_password"
DefaultClairEndpoint = "http://clair:6060"
)

View File

@ -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,
}

View File

@ -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)
}

View File

@ -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" {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -31,7 +31,7 @@ export const ENDPOINT_TEMPLATE: string = `
<clr-dg-cell>{{t.creation_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
{{pagination.totalItems}} {{'DESTINATION.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
</clr-dg-footer>

View File

@ -34,7 +34,7 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPLICATION.OF' | translate}} {{pagination.totalItems }} {{'REPLICATION.ITEMS' | translate}}
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPLICATION.OF' | translate}} </span>{{pagination.totalItems }} {{'REPLICATION.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -13,7 +13,7 @@ export const LIST_REPOSITORY_TEMPLATE = `
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
{{pagination.totalItems}}{{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
</clr-dg-footer>

View File

@ -67,7 +67,7 @@ export const REPLICATION_TEMPLATE: string = `
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}}
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPLICATION.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination>
</clr-dg-footer>

View File

@ -30,7 +30,7 @@ export const REPOSITORY_STACKVIEW_TEMPLATE: string = `
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}
</span>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>

View File

@ -36,7 +36,7 @@ export abstract class EndpointService {
* @abstract
* @param {(number | string)} endpointId
* @returns {(Observable<Endpoint> | Endpoint)}
*
*
* @memberOf EndpointService
*/
abstract getEndpoint(endpointId: number | string): Observable<Endpoint> | Promise<Endpoint> | 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()

View File

@ -53,7 +53,7 @@ export const TAG_TEMPLATE = `
<clr-dg-cell style="width: 80px;" *ngIf="!withClair">{{t.os}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}&nbsp;&nbsp;&nbsp;&nbsp;
<clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination>
</clr-dg-footer>

View File

@ -78,7 +78,8 @@ export const GRID_COMPONENT_HTML: string = `
</clr-dg-row>
<clr-dg-footer>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'VULNERABILITY.GRID.FOOT_OF' | translate}} {{pagination.totalItems}} {{'VULNERABILITY.GRID.FOOT_ITEMS' | translate}}
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'VULNERABILITY.GRID.FOOT_OF' | translate}}</span>
{{pagination.totalItems}} {{'VULNERABILITY.GRID.FOOT_ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="25" [clrDgTotalItems]="scanningResults.length"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -17,7 +17,7 @@
<label for="tabsystem">{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}</label>
<ul class="nav-list">
<li><a class="nav-link nav-link-override" routerLink="/harbor/users" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</a></li>
<li><a class="nav-link nav-link-override" routerLink="/harbor/replications/endpoints" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a></li>
<li><a class="nav-link nav-link-override" routerLink="/harbor/replications" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</a></li>
<li><a class="nav-link nav-link-override" routerLink="/harbor/configs" routerLinkActive="active">{{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}}</a></li>
</ul>
</section>

View File

@ -90,6 +90,10 @@ const harborRoutes: Routes = [
{
path: 'endpoints',
component: DestinationPageComponent
},
{
path: '**',
redirectTo: 'endpoints'
}
]
},

View File

@ -56,8 +56,11 @@
<clr-dg-cell>{{l.op_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{totalRecordCount}} {{'AUDIT_LOG.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'AUDIT_LOG.OF' | translate}} </span>
{{pagination.totalItems }} {{'AUDIT_LOG.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
<!--{{totalRecordCount}} {{'AUDIT_LOG.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>-->
</clr-dg-footer>
</clr-datagrid>
</div>

View File

@ -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);
}
}
}

View File

@ -17,7 +17,8 @@
<clr-dg-cell>{{p.creation_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{(projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="15"></clr-dg-pagination>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span>
{{pagination.totalItems }} {{'PROJECT.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -27,7 +27,11 @@
<clr-dg-cell>{{m.username}}</clr-dg-cell>
<clr-dg-cell>{{roleInfo[m.role_id] | translate}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer>
<clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'MEMBER.OF' | translate}} </span>
{{pagination.totalItems }} {{'MEMBER.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>

View File

@ -10,7 +10,10 @@
<clr-dg-cell>{{p.creation_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{(projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="15"></clr-dg-pagination>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span>
{{pagination.totalItems }} {{'PROJECT.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
<!--{{(projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="15"></clr-dg-pagination>-->
</clr-dg-footer>
</clr-datagrid>

View File

@ -8,7 +8,8 @@
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{(repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="15"></clr-dg-pagination>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPOSITORY.OF' | translate}} </span>
{{pagination.totalItems }} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -27,8 +27,10 @@
<clr-dg-cell>{{user.email}}</clr-dg-cell>
<clr-dg-cell>{{user.creation_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{pagination.totalItems}} users
<clr-dg-pagination #pagination [clrDgPageSize]="15" [(clrDgPage)]="currentPage" [clrDgTotalItems]="totalCount"> {{'USER.ITEMS' | translate}}
<clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'USER.OF' | translate }}</span>
{{pagination.totalItems}} {{'USER.ITEMS' | translate }}
<clr-dg-pagination #pagination [clrDgPageSize]="15" [(clrDgPage)]="currentPage" [clrDgTotalItems]="totalCount">
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -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.",

View File

@ -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.",

View File

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