Merge branch 'master' into bugx_fix

This commit is contained in:
Deng, Qian 2018-01-19 15:43:14 +08:00
commit e240290bc7
24 changed files with 249 additions and 251 deletions

View File

@ -1511,6 +1511,18 @@ paths:
format: int64 format: int64
required: false required: false
description: Relevant project ID. description: Relevant project ID.
- name: page
in: query
type: integer
format: int32
required: false
description: 'The page nubmer.'
- name: page_size
in: query
type: integer
format: int32
required: false
description: 'The size of per page.'
tags: tags:
- Products - Products
responses: responses:

View File

@ -365,7 +365,13 @@ func GetDatabaseFromCfg(cfg map[string]interface{}) *models.Database {
// Valid LDAP Scope // Valid LDAP Scope
func validLdapScope(cfg map[string]interface{}, isMigrate bool) { func validLdapScope(cfg map[string]interface{}, isMigrate bool) {
ldapScope := cfg[ldapScopeKey].(int) ldapScope, ok := cfg[ldapScopeKey].(int)
if !ok {
ldapScopeFloat, ok := cfg[ldapScopeKey].(float64)
if ok {
ldapScope = int(ldapScopeFloat)
}
}
if isMigrate && ldapScope > 0 && ldapScope < 3 { if isMigrate && ldapScope > 0 && ldapScope < 3 {
ldapScope = ldapScope - 1 ldapScope = ldapScope - 1
} }

View File

@ -143,6 +143,8 @@ func TestGetDatabaseFromCfg(t *testing.T) {
} }
func TestValidLdapScope(t *testing.T) { func TestValidLdapScope(t *testing.T) {
var dbValue float64
dbValue = 2
ldapScopeKey := "ldap_scope" ldapScopeKey := "ldap_scope"
testCfgs := []struct { testCfgs := []struct {
config map[string]interface{} config map[string]interface{}
@ -167,6 +169,9 @@ func TestValidLdapScope(t *testing.T) {
{map[string]interface{}{ {map[string]interface{}{
ldapScopeKey: -100, ldapScopeKey: -100,
}, false, 0}, }, false, 0},
{map[string]interface{}{
ldapScopeKey: dbValue,
}, false, 2},
} }
for i, item := range testCfgs { for i, item := range testCfgs {

View File

@ -21,6 +21,7 @@ import (
"github.com/astaxie/beego/orm" "github.com/astaxie/beego/orm"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils"
@ -1253,8 +1254,13 @@ func TestDeleteRepTarget(t *testing.T) {
} }
} }
func TestGetTotalOfRepPolicies(t *testing.T) {
_, err := GetTotalOfRepPolicies("", 1)
require.Nil(t, err)
}
func TestFilterRepPolicies(t *testing.T) { func TestFilterRepPolicies(t *testing.T) {
_, err := FilterRepPolicies("name", 0) _, err := FilterRepPolicies("name", 0, 0, 0)
if err != nil { if err != nil {
t.Fatalf("failed to filter policy: %v", err) t.Fatalf("failed to filter policy: %v", err)
} }

View File

@ -139,8 +139,23 @@ func GetRepPolicy(id int64) (*models.RepPolicy, error) {
return &policy, nil return &policy, nil
} }
// GetTotalOfRepPolicies returns the total count of replication policies
func GetTotalOfRepPolicies(name string, projectID int64) (int64, error) {
qs := GetOrmer().QueryTable(&models.RepPolicy{}).Filter("deleted", 0)
if len(name) != 0 {
qs = qs.Filter("name__icontains", name)
}
if projectID != 0 {
qs = qs.Filter("project_id", projectID)
}
return qs.Count()
}
// FilterRepPolicies filters policies by name and project ID // FilterRepPolicies filters policies by name and project ID
func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error) { func FilterRepPolicies(name string, projectID, page, pageSize int64) ([]*models.RepPolicy, error) {
o := GetOrmer() o := GetOrmer()
var args []interface{} var args []interface{}
@ -170,6 +185,11 @@ func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error
sql += `group by rp.id order by rp.creation_time` sql += `group by rp.id order by rp.creation_time`
if page > 0 && pageSize > 0 {
sql += ` limit ? offset ?`
args = append(args, pageSize, (page-1)*pageSize)
}
var policies []*models.RepPolicy var policies []*models.RepPolicy
if _, err := o.Raw(sql, args).QueryRows(&policies); err != nil { if _, err := o.Raw(sql, args).QueryRows(&policies); err != nil {
return nil, err return nil, err

View File

@ -22,8 +22,8 @@ import (
type FakePolicyManager struct { type FakePolicyManager struct {
} }
func (f *FakePolicyManager) GetPolicies(query models.QueryParameter) ([]models.ReplicationPolicy, error) { func (f *FakePolicyManager) GetPolicies(query models.QueryParameter) (*models.ReplicationPolicyQueryResult, error) {
return []models.ReplicationPolicy{}, nil return &models.ReplicationPolicyQueryResult{}, nil
} }
func (f *FakePolicyManager) GetPolicy(id int64) (models.ReplicationPolicy, error) { func (f *FakePolicyManager) GetPolicy(id int64) (models.ReplicationPolicy, error) {

View File

@ -98,18 +98,17 @@ func (ctl *DefaultController) Init() error {
return nil return nil
} }
//Build query parameters policies, err := ctl.policyManager.GetPolicies(models.QueryParameter{})
query := models.QueryParameter{
TriggerType: replication.TriggerKindSchedule,
}
policies, err := ctl.policyManager.GetPolicies(query)
if err != nil { if err != nil {
return err return err
} }
if policies != nil && len(policies) > 0 { if policies != nil && len(policies.Policies) > 0 {
for _, policy := range policies { for _, policy := range policies.Policies {
if err := ctl.triggerManager.SetupTrigger(&policy); err != nil { if policy.Trigger == nil || policy.Trigger.Kind != replication.TriggerKindSchedule {
continue
}
if err := ctl.triggerManager.SetupTrigger(policy); err != nil {
log.Errorf("failed to setup trigger for policy %v: %v", policy, err) log.Errorf("failed to setup trigger for policy %v: %v", policy, err)
} }
} }
@ -209,7 +208,7 @@ func (ctl *DefaultController) GetPolicy(policyID int64) (models.ReplicationPolic
} }
//GetPolicies is delegation of GetPoliciemodels.ReplicationPolicy{}s of Policy.Manager //GetPolicies is delegation of GetPoliciemodels.ReplicationPolicy{}s of Policy.Manager
func (ctl *DefaultController) GetPolicies(query models.QueryParameter) ([]models.ReplicationPolicy, error) { func (ctl *DefaultController) GetPolicies(query models.QueryParameter) (*models.ReplicationPolicyQueryResult, error) {
return ctl.policyManager.GetPolicies(query) return ctl.policyManager.GetPolicies(query)
} }

View File

@ -27,12 +27,15 @@ type QueryParameter struct {
//Size of each page, couple with page //Size of each page, couple with page
PageSize int64 PageSize int64
//Query by the type of trigger
TriggerType string
//Query by project ID //Query by project ID
ProjectID int64 ProjectID int64
//Query by name //Query by name
Name string Name string
} }
// ReplicationPolicyQueryResult is the query result of replication policy
type ReplicationPolicyQueryResult struct {
Total int64
Policies []*ReplicationPolicy
}

View File

@ -26,7 +26,7 @@ import (
// Manager defines the method a policy manger should implement // Manager defines the method a policy manger should implement
type Manager interface { type Manager interface {
GetPolicies(models.QueryParameter) ([]models.ReplicationPolicy, error) GetPolicies(models.QueryParameter) (*models.ReplicationPolicyQueryResult, error)
GetPolicy(int64) (models.ReplicationPolicy, error) GetPolicy(int64) (models.ReplicationPolicy, error)
CreatePolicy(models.ReplicationPolicy) (int64, error) CreatePolicy(models.ReplicationPolicy) (int64, error)
UpdatePolicy(models.ReplicationPolicy) error UpdatePolicy(models.ReplicationPolicy) error
@ -42,27 +42,28 @@ func NewDefaultManager() *DefaultManager {
} }
//GetPolicies returns all the policies //GetPolicies returns all the policies
func (m *DefaultManager) GetPolicies(query models.QueryParameter) ([]models.ReplicationPolicy, error) { func (m *DefaultManager) GetPolicies(query models.QueryParameter) (*models.ReplicationPolicyQueryResult, error) {
result := []models.ReplicationPolicy{} result := &models.ReplicationPolicyQueryResult{
//TODO support more query conditions other than name and project ID Policies: []*models.ReplicationPolicy{},
policies, err := dao.FilterRepPolicies(query.Name, query.ProjectID) }
total, err := dao.GetTotalOfRepPolicies(query.Name, query.ProjectID)
if err != nil { if err != nil {
return result, err return nil, err
}
result.Total = total
policies, err := dao.FilterRepPolicies(query.Name, query.ProjectID, query.Page, query.PageSize)
if err != nil {
return nil, err
} }
for _, policy := range policies { for _, policy := range policies {
ply, err := convertFromPersistModel(policy) ply, err := convertFromPersistModel(policy)
if err != nil { if err != nil {
return []models.ReplicationPolicy{}, err return nil, err
} }
if len(query.TriggerType) > 0 { result.Policies = append(result.Policies, &ply)
if ply.Trigger.Kind != query.TriggerType {
continue
}
}
result = append(result, ply)
} }
return result, nil return result, nil

View File

@ -82,7 +82,7 @@ func (ra *RepJobAPI) List() {
} }
repository := ra.GetString("repository") repository := ra.GetString("repository")
status := ra.GetString("status") statuses := ra.GetStrings("status")
var startTime *time.Time var startTime *time.Time
startTimeStr := ra.GetString("start_time") startTimeStr := ra.GetString("start_time")
@ -108,15 +108,11 @@ func (ra *RepJobAPI) List() {
page, pageSize := ra.GetPaginationParams() page, pageSize := ra.GetPaginationParams()
statuses := []string{}
if len(status) > 0 {
statuses = append(statuses, status)
}
jobs, total, err := dao.FilterRepJobs(policyID, repository, statuses, jobs, total, err := dao.FilterRepJobs(policyID, repository, statuses,
startTime, endTime, pageSize, pageSize*(page-1)) startTime, endTime, pageSize, pageSize*(page-1))
if err != nil { if err != nil {
log.Errorf("failed to filter jobs according policy ID %d, repository %s, status %s, start time %v, end time %v: %v", log.Errorf("failed to filter jobs according policy ID %d, repository %s, status %v, start time %v, end time %v: %v",
policyID, repository, status, startTime, endTime, err) policyID, repository, statuses, startTime, endTime, err)
ra.CustomAbort(http.StatusInternalServerError, "") ra.CustomAbort(http.StatusInternalServerError, "")
} }

View File

@ -89,28 +89,34 @@ func (pa *RepPolicyAPI) List() {
} }
queryParam.ProjectID = projectID queryParam.ProjectID = projectID
} }
queryParam.Page, queryParam.PageSize = pa.GetPaginationParams()
result := []*api_models.ReplicationPolicy{} result, err := core.GlobalController.GetPolicies(queryParam)
policies, err := core.GlobalController.GetPolicies(queryParam)
if err != nil { if err != nil {
log.Errorf("failed to get policies: %v, query parameters: %v", err, queryParam) log.Errorf("failed to get policies: %v, query parameters: %v", err, queryParam)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
} }
for _, policy := range policies { var total int64
if !pa.SecurityCtx.HasAllPerm(policy.ProjectIDs[0]) { policies := []*api_models.ReplicationPolicy{}
continue if result != nil {
total = result.Total
for _, policy := range result.Policies {
if !pa.SecurityCtx.HasAllPerm(policy.ProjectIDs[0]) {
continue
}
ply, err := convertFromRepPolicy(pa.ProjectMgr, *policy)
if err != nil {
pa.ParseAndHandleError(fmt.Sprintf("failed to convert from replication policy"), err)
return
}
policies = append(policies, ply)
} }
ply, err := convertFromRepPolicy(pa.ProjectMgr, policy)
if err != nil {
pa.ParseAndHandleError(fmt.Sprintf("failed to convert from replication policy"), err)
return
}
result = append(result, ply)
} }
pa.Data["json"] = result pa.SetPaginationHeader(total, queryParam.Page, queryParam.PageSize)
pa.Data["json"] = policies
pa.ServeJSON() pa.ServeJSON()
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "harbor-ui", "name": "harbor-ui",
"version": "0.6.22", "version": "0.6.25",
"description": "Harbor shared UI components based on Clarity and Angular4", "description": "Harbor shared UI components based on Clarity and Angular4",
"scripts": { "scripts": {
"start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json", "start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json",

View File

@ -1,6 +1,6 @@
{ {
"name": "harbor-ui", "name": "harbor-ui",
"version": "0.6.22", "version": "0.6.25",
"description": "Harbor shared UI components based on Clarity and Angular4", "description": "Harbor shared UI components based on Clarity and Angular4",
"author": "VMware", "author": "VMware",
"module": "index.js", "module": "index.js",

View File

@ -65,6 +65,10 @@ export class Configuration {
ldap_uid: StringValueItem; ldap_uid: StringValueItem;
ldap_url: StringValueItem; ldap_url: StringValueItem;
ldap_verify_cert: BoolValueItem; ldap_verify_cert: BoolValueItem;
uaa_client_id: StringValueItem;
uaa_client_secret?: StringValueItem;
uaa_endpoint: StringValueItem;
uaa_verify_cert: BoolValueItem;
email_host: StringValueItem; email_host: StringValueItem;
email_identity: StringValueItem; email_identity: StringValueItem;
email_from: StringValueItem; email_from: StringValueItem;
@ -91,6 +95,10 @@ export class Configuration {
this.ldap_uid = new StringValueItem("", true); this.ldap_uid = new StringValueItem("", true);
this.ldap_url = new StringValueItem("", true); this.ldap_url = new StringValueItem("", true);
this.ldap_verify_cert = new BoolValueItem(true, true); this.ldap_verify_cert = new BoolValueItem(true, true);
this.uaa_client_id = new StringValueItem("", true);
this.uaa_client_secret = new StringValueItem("", true);
this.uaa_endpoint = new StringValueItem("", true);
this.uaa_verify_cert = new BoolValueItem(false, true);
this.email_host = new StringValueItem("", true); this.email_host = new StringValueItem("", true);
this.email_identity = new StringValueItem("", true); this.email_identity = new StringValueItem("", true);
this.email_from = new StringValueItem("", true); this.email_from = new StringValueItem("", true);

View File

@ -9,18 +9,15 @@
"check-space" "check-space"
], ],
"curly": true, "curly": true,
"eofline": true, "eofline": false,
"forin": true, "forin": false,
"indent": [ "indent": [
true, true,
"spaces" "spaces"
], ],
"label-position": true, "label-position": true,
"label-undefined": true, "label-undefined": true,
"max-line-length": [ "max-line-length": false,
false,
140
],
"member-access": false, "member-access": false,
"member-ordering": [ "member-ordering": [
true, true,
@ -74,7 +71,8 @@
], ],
"quotemark": [ "quotemark": [
true, true,
"single" "double",
"avoid-escape"
], ],
"radix": true, "radix": true,
"semicolon": [ "semicolon": [

View File

@ -31,7 +31,7 @@
"clarity-icons": "^0.10.17", "clarity-icons": "^0.10.17",
"clarity-ui": "^0.10.17", "clarity-ui": "^0.10.17",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"harbor-ui": "0.6.25-dev.3", "harbor-ui": "0.6.25",
"intl": "^1.2.5", "intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2", "mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0", "ngx-cookie": "^1.0.0",

View File

@ -1,6 +1,6 @@
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="false"> <clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="false">
<h3 class="modal-title">{{'PROFILE.TITLE' | translate}}</h3> <h3 class="modal-title">{{'PROFILE.TITLE' | translate}}</h3>
<inline-alert class="modal-title" (confirmEvt)="confirm($event)"></inline-alert> <inline-alert class="modal-title" (confirmEvt)="confirmYes($event)" (closeEvt)="confirmNo($event)"></inline-alert>
<div class="modal-body" style="overflow-y: hidden;"> <div class="modal-body" style="overflow-y: hidden;">
<form #accountSettingsFrom="ngForm" class="form"> <form #accountSettingsFrom="ngForm" class="form">
<section class="form-block"> <section class="form-block">

View File

@ -240,8 +240,13 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
}); });
} }
confirm($event: any): void { confirmNo($event: any): void {
if(this.RenameOnGoing) { if (this.RenameOnGoing) {
this.RenameOnGoing = false;
}
}
confirmYes($event: any): void {
if (this.RenameOnGoing) {
this.confirmRename(); this.confirmRename();
this.RenameOnGoing = false; this.RenameOnGoing = false;
} }

View File

@ -165,7 +165,7 @@
<clr-checkbox name="selfReg" id="selfReg" [(ngModel)]="currentConfig.self_registration.value" [disabled]="disabled(currentConfig.self_registration)"> <clr-checkbox name="selfReg" id="selfReg" [(ngModel)]="currentConfig.self_registration.value" [disabled]="disabled(currentConfig.self_registration)">
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-7px;"> <a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-7px;">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon> <clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span *ngIf="checkboxenable; else elseBlock" class="tooltip-content">{{'CONFIG.TOOLTIP.SELF_REGISTRATION_ENABLE' | translate}}</span> <span *ngIf="checkable; else elseBlock" class="tooltip-content">{{'CONFIG.TOOLTIP.SELF_REGISTRATION_ENABLE' | translate}}</span>
<ng-template #elseBlock><span class="tooltip-content">{{'CONFIG.TOOLTIP.SELF_REGISTRATION_DISABLE' | translate}}</span></ng-template> <ng-template #elseBlock><span class="tooltip-content">{{'CONFIG.TOOLTIP.SELF_REGISTRATION_DISABLE' | translate}}</span></ng-template>
</a> </a>
</clr-checkbox> </clr-checkbox>

View File

@ -19,15 +19,16 @@ import { Configuration } from 'harbor-ui';
@Component({ @Component({
selector: 'config-auth', selector: 'config-auth',
templateUrl: "config-auth.component.html", templateUrl: 'config-auth.component.html',
styleUrls: ['../config.component.css'] styleUrls: ['../config.component.css']
}) })
export class ConfigurationAuthComponent implements OnChanges { export class ConfigurationAuthComponent implements OnChanges {
changeSub: Subscription; changeSub: Subscription;
@Input("allConfig") currentConfig: Configuration = new Configuration(); @Input('allConfig') currentConfig: Configuration = new Configuration();
@ViewChild("authConfigFrom") authForm: NgForm; @ViewChild('authConfigFrom') authForm: NgForm;
constructor() { }
ngOnChanges(): void { ngOnChanges(): void {
if ( this.currentConfig && if ( this.currentConfig &&
this.currentConfig.auth_mode && this.currentConfig.auth_mode &&
@ -36,14 +37,12 @@ export class ConfigurationAuthComponent implements OnChanges {
} }
} }
get checkboxenable(){ get checkable(){
return this.currentConfig && return this.currentConfig &&
this.currentConfig.self_registration && this.currentConfig.self_registration &&
this.currentConfig.self_registration.value === true; this.currentConfig.self_registration.value === true;
} }
constructor() { }
public get showLdap(): boolean { public get showLdap(): boolean {
return this.currentConfig && return this.currentConfig &&
this.currentConfig.auth_mode && this.currentConfig.auth_mode &&
@ -62,6 +61,10 @@ export class ConfigurationAuthComponent implements OnChanges {
} }
} }
public isValid(): boolean {
return this.authForm && this.authForm.valid;
}
setVerifyCertValue($event: any) { setVerifyCertValue($event: any) {
this.currentConfig.ldap_verify_cert.value = $event; this.currentConfig.ldap_verify_cert.value = $event;
} }
@ -70,16 +73,12 @@ export class ConfigurationAuthComponent implements OnChanges {
return !(prop && prop.editable); return !(prop && prop.editable);
} }
public isValid(): boolean {
return this.authForm && this.authForm.valid;
}
handleOnChange($event: any): void { handleOnChange($event: any): void {
if ($event && $event.target && $event.target["value"]) { if ($event && $event.target && $event.target["value"]) {
let authMode = $event.target["value"]; let authMode = $event.target["value"];
if (authMode === 'ldap_auth' || authMode === 'uaa_auth') { if (authMode === 'ldap_auth' || authMode === 'uaa_auth') {
if (this.currentConfig.self_registration.value) { if (this.currentConfig.self_registration.value) {
this.currentConfig.self_registration.value = false;//uncheck this.currentConfig.self_registration.value = false; // uncheck
} }
} }
} }

View File

@ -1,62 +0,0 @@
<div class="config-container">
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
<clr-tabs (clrTabsCurrentTabLinkChanged)="tabLinkChanged($event)">
<clr-tab-link [clrTabLinkId]="'config-auth'" [clrTabLinkActive]='isCurrentTabLink("config-auth")'>{{'CONFIG.AUTH' | translate }}</clr-tab-link>
<clr-tab-link [clrTabLinkId]="'config-replication'" [clrTabLinkActive]='isCurrentTabLink("config-replication")'>{{'CONFIG.REPLICATION' | translate }}</clr-tab-link>
<clr-tab-link [clrTabLinkId]="'config-email'" [clrTabLinkActive]='isCurrentTabLink("config-email")'>{{'CONFIG.EMAIL' | translate }}</clr-tab-link>
<clr-tab-link [clrTabLinkId]="'config-system'" [clrTabLinkActive]='isCurrentTabLink("config-system")'>{{'CONFIG.SYSTEM' | translate }}</clr-tab-link>
<clr-tab-content [clrTabContentId]="'authentication'" [clrTabContentActive]='isCurrentTabContent("authentication")'>
<config-auth [ldapConfig]="allConfig"></config-auth>
</clr-tab-content>
<clr-tab-content [clrTabContentId]="'replication'" [clrTabContentActive]='isCurrentTabContent("replication")'>
<form #repoConfigFrom="ngForm" class="form">
<section class="form-block">
<div class="form-group">
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
</a>
</clr-checkbox>
</div>
</section>
</form>
</clr-tab-content>
<clr-tab-content [clrTabContentId]="'email'" [clrTabContentActive]='isCurrentTabContent("email")'>
<config-email [mailConfig]="allConfig"></config-email>
</clr-tab-content>
<clr-tab-content [clrTabContentId]="'system_settings'" [clrTabContentActive]='isCurrentTabContent("system_settings")'>
<form #systemConfigFrom="ngForm" class="form">
<section class="form-block">
<div class="form-group">
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
<input name="tokenExpiration" type="text" #tokenExpirationInput="ngModel" [(ngModel)]="allConfig.token_expiration.value"
required
pattern="^[1-9]{1}[\d]*$"
id="tokenExpiration"
size="40" [disabled]="disabled(allConfig.token_expiration)">
<span class="tooltip-content">
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
</span>
</label>
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
</a>
</div>
</section>
</form>
</clr-tab-content>
</clr-tabs>
<div>
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-outline" (click)="testMailServer()" *ngIf="showTestServerBtn" [disabled]="!isMailConfigValid()">{{'BUTTON.TEST_MAIL' | translate}}</button>
<button type="button" class="btn btn-outline" (click)="testLDAPServer()" *ngIf="showLdapServerBtn" [disabled]="!isLDAPConfigValid()">{{'BUTTON.TEST_LDAP' | translate}}</button>
<span class="spinner spinner-inline" [hidden]="!testingInProgress"></span>
</div>
</div>

View File

@ -1,24 +1,23 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved. // Copyright (c) 2017 VMware, Inc. All Rights Reserved.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // http://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { ConfigurationService } from './config.service'; import { ConfigurationService } from './config.service';
import { ConfirmationTargets, ConfirmationState } from '../shared/shared.const';; import { ConfirmationTargets, ConfirmationState } from '../shared/shared.const';
import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service'; import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message' import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message';
import { ConfigurationAuthComponent } from './auth/config-auth.component'; import { ConfigurationAuthComponent } from './auth/config-auth.component';
import { ConfigurationEmailComponent } from './email/config-email.component'; import { ConfigurationEmailComponent } from './email/config-email.component';
@ -29,24 +28,22 @@ import { MessageHandlerService } from '../shared/message-handler/message-handler
import { import {
Configuration, Configuration,
StringValueItem, StringValueItem,
ComplexValueItem,
SystemSettingsComponent, SystemSettingsComponent,
VulnerabilityConfigComponent, VulnerabilityConfigComponent,
ClairDBStatus
} from 'harbor-ui'; } from 'harbor-ui';
const fakePass = "aWpLOSYkIzJTTU4wMDkx"; const fakePass = 'aWpLOSYkIzJTTU4wMDkx';
const TabLinkContentMap = { const TabLinkContentMap = {
"config-auth": "authentication", 'config-auth': 'authentication',
"config-replication": "replication", 'config-replication': 'replication',
"config-email": "email", 'config-email': 'email',
"config-system": "system_settings", 'config-system': 'system_settings',
"config-vulnerability": "vulnerability" 'config-vulnerability': 'vulnerability'
}; };
@Component({ @Component({
selector: 'config', selector: 'config',
templateUrl: "config.component.html", templateUrl: 'config.component.html',
styleUrls: ['config.component.css'] styleUrls: ['config.component.css']
}) })
export class ConfigurationComponent implements OnInit, OnDestroy { export class ConfigurationComponent implements OnInit, OnDestroy {
@ -99,36 +96,36 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
let properties = []; let properties = [];
switch (this.currentTabId) { switch (this.currentTabId) {
case "config-auth": case 'config-auth':
for (let prop in allChanges) { for (let prop in allChanges) {
if (prop.startsWith("ldap_")) { if (prop.startsWith('ldap_')) {
return allChanges; return allChanges;
} }
} }
properties = ["auth_mode", "project_creation_restriction", "self_registration"]; properties = ['auth_mode', 'project_creation_restriction', 'self_registration'];
break; break;
case "config-email": case 'config-email':
for (let prop in allChanges) { for (let prop in allChanges) {
if (prop.startsWith("email_")) { if (prop.startsWith('email_')) {
return allChanges; return allChanges;
} }
} }
return null; return null;
case "config-replication": case 'config-replication':
properties = ["verify_remote_cert"]; properties = ['verify_remote_cert'];
break; break;
case "config-system": case 'config-system':
properties = ["token_expiration"]; properties = ['token_expiration'];
break; break;
case "config-vulnerability": case 'config-vulnerability':
properties = ["scan_all_policy"]; properties = ['scan_all_policy'];
break; break;
default: default:
return null; return null;
} }
for (let prop in allChanges) { for (let prop in allChanges) {
if (properties.indexOf(prop) != -1) { if (properties.indexOf(prop) !== -1) {
return allChanges; return allChanges;
} }
} }
@ -137,8 +134,8 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
//First load // First load
//Double confirm the current use has admin role // Double confirm the current use has admin role
let currentUser = this.session.getCurrentUser(); let currentUser = this.session.getCurrentUser();
if (currentUser && currentUser.has_admin_role > 0) { if (currentUser && currentUser.has_admin_role > 0) {
this.retrieveConfig(); this.retrieveConfig();
@ -150,8 +147,8 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
if (confirmation.source === ConfirmationTargets.CONFIG) { if (confirmation.source === ConfirmationTargets.CONFIG) {
this.reset(confirmation.data); this.reset(confirmation.data);
} else if (confirmation.source === ConfirmationTargets.CONFIG_TAB) { } else if (confirmation.source === ConfirmationTargets.CONFIG_TAB) {
this.reset(confirmation.data["changes"]); this.reset(confirmation.data['changes']);
this.currentTabId = confirmation.data["tabId"]; this.currentTabId = confirmation.data['tabId'];
} }
} }
}); });
@ -200,7 +197,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
public get showLdapServerBtn(): boolean { public get showLdapServerBtn(): boolean {
return this.currentTabId === 'config-auth' && return this.currentTabId === 'config-auth' &&
this.allConfig.auth_mode && this.allConfig.auth_mode &&
this.allConfig.auth_mode.value === "ldap_auth"; this.allConfig.auth_mode.value === 'ldap_auth';
} }
public get hideMailTestingSpinner(): boolean { public get hideMailTestingSpinner(): boolean {
@ -218,7 +215,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
} }
public tabLinkClick(tabLink: string) { public tabLinkClick(tabLink: string) {
//Whether has unsaved changes in current tab // Whether has unsaved changes in current tab
let changes = this.hasUnsavedChangesOfCurrentTab(); let changes = this.hasUnsavedChangesOfCurrentTab();
if (!changes) { if (!changes) {
this.currentTabId = tabLink; this.currentTabId = tabLink;
@ -229,18 +226,18 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
} }
/** /**
* *
* Save the changed values * Save the changed values
* *
* @memberOf ConfigurationComponent * @memberOf ConfigurationComponent
*/ */
public save(): void { public save(): void {
let changes = this.getChanges(); let changes = this.getChanges();
if (!this.isEmpty(changes)) { if (!this.isEmpty(changes)) {
//Fix policy parameters issue // Fix policy parameters issue
let scanningAllPolicy = changes["scan_all_policy"]; let scanningAllPolicy = changes['scan_all_policy'];
if (scanningAllPolicy && if (scanningAllPolicy &&
scanningAllPolicy.type !== "daily" && scanningAllPolicy.type !== 'daily' &&
scanningAllPolicy.parameters) { scanningAllPolicy.parameters) {
delete (scanningAllPolicy.parameters); delete (scanningAllPolicy.parameters);
} }
@ -249,32 +246,32 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
this.configService.saveConfiguration(changes) this.configService.saveConfiguration(changes)
.then(response => { .then(response => {
this.onGoing = false; this.onGoing = false;
//API should return the updated configurations here // API should return the updated configurations here
//Unfortunately API does not do that // Unfortunately API does not do that
//To refresh the view, we can clone the original data copy // To refresh the view, we can clone the original data copy
//or force refresh by calling service. // or force refresh by calling service.
//HERE we choose force way // HERE we choose force way
this.retrieveConfig(); this.retrieveConfig();
//Reload bootstrap option // Reload bootstrap option
this.appConfigService.load().catch(error => console.error("Failed to reload bootstrap option with error: ", error)); this.appConfigService.load().catch(error => console.error('Failed to reload bootstrap option with error: ', error));
this.msgHandler.showSuccess("CONFIG.SAVE_SUCCESS"); this.msgHandler.showSuccess('CONFIG.SAVE_SUCCESS');
}) })
.catch(error => { .catch(error => {
this.onGoing = false; this.onGoing = false;
this.msgHandler.handleError(error); this.msgHandler.handleError(error);
}); });
} else { } else {
//Inprop situation, should not come here // Inprop situation, should not come here
console.error("Save obort becasue nothing changed"); console.error('Save obort becasue nothing changed');
} }
} }
/** /**
* *
* Discard current changes if have and reset * Discard current changes if have and reset
* *
* @memberOf ConfigurationComponent * @memberOf ConfigurationComponent
*/ */
public cancel(): void { public cancel(): void {
@ -282,98 +279,98 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
if (!this.isEmpty(changes)) { if (!this.isEmpty(changes)) {
this.confirmUnsavedChanges(changes); this.confirmUnsavedChanges(changes);
} else { } else {
//Invalid situation, should not come here // Invalid situation, should not come here
console.error("Nothing changed"); console.error('Nothing changed');
} }
} }
/** /**
* *
* Test the connection of specified mail server * Test the connection of specified mail server
* *
* *
* @memberOf ConfigurationComponent * @memberOf ConfigurationComponent
*/ */
public testMailServer(): void { public testMailServer(): void {
if (this.testingMailOnGoing) { if (this.testingMailOnGoing) {
return;//Should not come here return; // Should not come here
} }
let mailSettings = {}; let mailSettings = {};
for (let prop in this.allConfig) { for (let prop in this.allConfig) {
if (prop.startsWith("email_")) { if (prop.startsWith('email_')) {
mailSettings[prop] = this.allConfig[prop].value; mailSettings[prop] = this.allConfig[prop].value;
} }
} }
//Confirm port is number // Confirm port is number
mailSettings["email_port"] = +mailSettings["email_port"]; mailSettings['email_port'] = +mailSettings['email_port'];
let allChanges = this.getChanges(); let allChanges = this.getChanges();
let password = allChanges["email_password"] let password = allChanges['email_password'];
if (password) { if (password) {
mailSettings["email_password"] = password; mailSettings['email_password'] = password;
} else { } else {
delete mailSettings["email_password"]; delete mailSettings['email_password'];
} }
this.testingMailOnGoing = true; this.testingMailOnGoing = true;
this.configService.testMailServer(mailSettings) this.configService.testMailServer(mailSettings)
.then(response => { .then(response => {
this.testingMailOnGoing = false; this.testingMailOnGoing = false;
this.msgHandler.showSuccess("CONFIG.TEST_MAIL_SUCCESS"); this.msgHandler.showSuccess('CONFIG.TEST_MAIL_SUCCESS');
}) })
.catch(error => { .catch(error => {
this.testingMailOnGoing = false; this.testingMailOnGoing = false;
let err = error._body; let err = error._body;
if (!err) { if (!err) {
err = "UNKNOWN"; err = 'UNKNOWN';
} }
this.msgHandler.showError("CONFIG.TEST_MAIL_FAILED", { 'param': err }); this.msgHandler.showError('CONFIG.TEST_MAIL_FAILED', { 'param': err });
}); });
} }
public testLDAPServer(): void { public testLDAPServer(): void {
if (this.testingLDAPOnGoing) { if (this.testingLDAPOnGoing) {
return;//Should not come here return; // Should not come here
} }
let ldapSettings = {}; let ldapSettings = {};
for (let prop in this.allConfig) { for (let prop in this.allConfig) {
if (prop.startsWith("ldap_")) { if (prop.startsWith('ldap_')) {
ldapSettings[prop] = this.allConfig[prop].value; ldapSettings[prop] = this.allConfig[prop].value;
} }
} }
let allChanges = this.getChanges(); let allChanges = this.getChanges();
let ldapSearchPwd = allChanges["ldap_search_password"]; let ldapSearchPwd = allChanges['ldap_search_password'];
if (ldapSearchPwd) { if (ldapSearchPwd) {
ldapSettings['ldap_search_password'] = ldapSearchPwd; ldapSettings['ldap_search_password'] = ldapSearchPwd;
} else { } else {
delete ldapSettings['ldap_search_password']; delete ldapSettings['ldap_search_password'];
} }
//Fix: Confirm ldap scope is number // Fix: Confirm ldap scope is number
ldapSettings['ldap_scope'] = +ldapSettings['ldap_scope']; ldapSettings['ldap_scope'] = +ldapSettings['ldap_scope'];
this.testingLDAPOnGoing = true; this.testingLDAPOnGoing = true;
this.configService.testLDAPServer(ldapSettings) this.configService.testLDAPServer(ldapSettings)
.then(respone => { .then(respone => {
this.testingLDAPOnGoing = false; this.testingLDAPOnGoing = false;
this.msgHandler.showSuccess("CONFIG.TEST_LDAP_SUCCESS"); this.msgHandler.showSuccess('CONFIG.TEST_LDAP_SUCCESS');
}) })
.catch(error => { .catch(error => {
this.testingLDAPOnGoing = false; this.testingLDAPOnGoing = false;
let err = error._body; let err = error._body;
if (!err) { if (!err) {
err = "UNKNOWN"; err = 'UNKNOWN';
} }
this.msgHandler.showError("CONFIG.TEST_LDAP_FAILED", { 'param': err }); this.msgHandler.showError('CONFIG.TEST_LDAP_FAILED', { 'param': err });
}); });
} }
confirmUnsavedChanges(changes: any) { confirmUnsavedChanges(changes: any) {
let msg = new ConfirmationMessage( let msg = new ConfirmationMessage(
"CONFIG.CONFIRM_TITLE", 'CONFIG.CONFIRM_TITLE',
"CONFIG.CONFIRM_SUMMARY", 'CONFIG.CONFIRM_SUMMARY',
"", '',
changes, changes,
ConfirmationTargets.CONFIG ConfirmationTargets.CONFIG
); );
@ -383,12 +380,12 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
confirmUnsavedTabChanges(changes: any, tabId: string) { confirmUnsavedTabChanges(changes: any, tabId: string) {
let msg = new ConfirmationMessage( let msg = new ConfirmationMessage(
"CONFIG.CONFIRM_TITLE", 'CONFIG.CONFIRM_TITLE',
"CONFIG.CONFIRM_SUMMARY", 'CONFIG.CONFIRM_SUMMARY',
"", '',
{ {
"changes": changes, 'changes': changes,
"tabId": tabId 'tabId': tabId
}, },
ConfirmationTargets.CONFIG_TAB ConfirmationTargets.CONFIG_TAB
); );
@ -402,12 +399,12 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
.then((configurations: Configuration) => { .then((configurations: Configuration) => {
this.onGoing = false; this.onGoing = false;
//Add two password fields // Add two password fields
configurations.email_password = new StringValueItem(fakePass, true); configurations.email_password = new StringValueItem(fakePass, true);
configurations.ldap_search_password = new StringValueItem(fakePass, true); configurations.ldap_search_password = new StringValueItem(fakePass, true);
configurations.uaa_client_secret = new StringValueItem(fakePass, false); configurations.uaa_client_secret = new StringValueItem(fakePass, true);
this.allConfig = configurations; this.allConfig = configurations;
//Keep the original copy of the data // Keep the original copy of the data
this.originalCopy = this.clone(configurations); this.originalCopy = this.clone(configurations);
}) })
.catch(error => { .catch(error => {
@ -417,12 +414,12 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
} }
/** /**
* *
* Get the changed fields and return a map * Get the changed fields and return a map
* *
* @private * @private
* @returns {*} * @returns {*}
* *
* @memberOf ConfigurationComponent * @memberOf ConfigurationComponent
*/ */
getChanges(): { [key: string]: any | any[] } { getChanges(): { [key: string]: any | any[] } {
@ -435,13 +432,13 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
if (field && field.editable) { if (field && field.editable) {
if (!this.compareValue(field.value, this.allConfig[prop].value)) { if (!this.compareValue(field.value, this.allConfig[prop].value)) {
changes[prop] = this.allConfig[prop].value; changes[prop] = this.allConfig[prop].value;
//Number // Number
if (typeof field.value === "number") { if (typeof field.value === 'number') {
changes[prop] = +changes[prop]; changes[prop] = +changes[prop];
} }
//Trim string value // Trim string value
if (typeof field.value === "string") { if (typeof field.value === 'string') {
changes[prop] = ('' + changes[prop]).trim(); changes[prop] = ('' + changes[prop]).trim();
} }
} }
@ -451,44 +448,44 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
return changes; return changes;
} }
//private // private
compareValue(a: any, b: any): boolean { compareValue(a: any, b: any): boolean {
if ((a && !b) || (!a && b)) return false; if ((a && !b) || (!a && b)) { return false; };
if (!a && !b) return true; if (!a && !b) { return true; };
return JSON.stringify(a) === JSON.stringify(b); return JSON.stringify(a) === JSON.stringify(b);
} }
//private // private
isEmpty(obj: any): boolean { isEmpty(obj: any): boolean {
return !obj || JSON.stringify(obj) === "{}"; return !obj || JSON.stringify(obj) === '{}';
} }
/** /**
* *
* Deep clone the configuration object * Deep clone the configuration object
* *
* @private * @private
* @param {Configuration} src * @param {Configuration} src
* @returns {Configuration} * @returns {Configuration}
* *
* @memberOf ConfigurationComponent * @memberOf ConfigurationComponent
*/ */
clone(src: Configuration): Configuration { clone(src: Configuration): Configuration {
if (!src) { if (!src) {
return new Configuration();//Empty return new Configuration(); // Empty
} }
return JSON.parse(JSON.stringify(src)); return JSON.parse(JSON.stringify(src));
} }
/** /**
* *
* Reset the configuration form * Reset the configuration form
* *
* @private * @private
* @param {*} changes * @param {*} changes
* *
* @memberOf ConfigurationComponent * @memberOf ConfigurationComponent
*/ */
reset(changes: any): void { reset(changes: any): void {
@ -496,11 +493,10 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
for (let prop in changes) { for (let prop in changes) {
if (this.originalCopy[prop]) { if (this.originalCopy[prop]) {
this.allConfig[prop] = this.clone(this.originalCopy[prop]); this.allConfig[prop] = this.clone(this.originalCopy[prop]);
//this.allConfig[prop] = Object.assign({}, this.originalCopy[prop]);
} }
} }
} else { } else {
//force reset // force reset
this.retrieveConfig(); this.retrieveConfig();
} }
} }
@ -508,4 +504,4 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
disabled(prop: any): boolean { disabled(prop: any): boolean {
return !(prop && prop.editable); return !(prop && prop.editable);
} }
} }

View File

@ -35,6 +35,7 @@ export class InlineAlertComponent {
blinking: boolean = false; blinking: boolean = false;
@Output() confirmEvt = new EventEmitter<boolean>(); @Output() confirmEvt = new EventEmitter<boolean>();
@Output() closeEvt = new EventEmitter<boolean>();
constructor(private translate: TranslateService) { } constructor(private translate: TranslateService) { }
@ -82,9 +83,10 @@ export class InlineAlertComponent {
this.useAppLevelStyle = false; this.useAppLevelStyle = false;
} }
//Close alert // Close alert
public close(): void { public close(): void {
this.alertClose = true; this.alertClose = true;
this.closeEvt.emit(true);
} }
public blink() { public blink() {

View File

@ -9,18 +9,15 @@
"check-space" "check-space"
], ],
"curly": true, "curly": true,
"eofline": true, "eofline": false,
"forin": true, "forin": false,
"indent": [ "indent": [
true, true,
"spaces" "spaces"
], ],
"label-position": true, "label-position": true,
"label-undefined": true, "label-undefined": true,
"max-line-length": [ "max-line-length": false,
false,
140
],
"member-access": false, "member-access": false,
"member-ordering": [ "member-ordering": [
true, true,
@ -74,7 +71,8 @@
], ],
"quotemark": [ "quotemark": [
true, true,
"single" "double",
"avoid-escape"
], ],
"radix": true, "radix": true,
"semicolon": [ "semicolon": [