mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 02:35:17 +01:00
Merge branch 'master' into fix_issue_#2793
This commit is contained in:
commit
5c876621ec
@ -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
|
||||
|
@ -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
|
||||
|
10
make/prepare
10
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")
|
||||
|
@ -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",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -66,6 +66,7 @@ const (
|
||||
WithNotary = "with_notary"
|
||||
WithClair = "with_clair"
|
||||
ScanAllPolicy = "scan_all_policy"
|
||||
ClairDBPassword = "clair_db_password"
|
||||
|
||||
DefaultClairEndpoint = "http://clair:6060"
|
||||
)
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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" {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
@ -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}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="10"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -90,6 +90,10 @@ const harborRoutes: Routes = [
|
||||
{
|
||||
path: 'endpoints',
|
||||
component: DestinationPageComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: 'endpoints'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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": "无效日期。"
|
||||
|
Loading…
Reference in New Issue
Block a user