Merge pull request #4172 from pengpengshui/batchDelection

Modify replication list unsave when modify username or possword
This commit is contained in:
pengpengshui 2018-01-29 19:10:25 +08:00 committed by GitHub
commit f4270213ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 38 additions and 25 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -102,12 +102,12 @@ You can add members with different roles to an existing project. You can add a L
![browse project](img/new_add_member.png)
### Updating and removing members
You can update or remove a member by clicking the icon on the left.
You can check one or more members, then click `MEMBER ACTION`, choose one role to batch switch checked members's roles. You can also click `MEMBER.REMOVE` to batch remove checked members.
![browse project](img/new_remove_update_member.png)
## Replicating images
Images replication is used to replicate repositories from one Harbor instance to another.
Images replication is used to replicate repositories from one Harbor instance to another.
The function is project-oriented, and once the system administrator set a rule to one project, all repositories under the project that match the defined [filter](#replication-filter) patterns will be replicated to the remote registry when the [triggering condition](#replication-triggering-condition) is triggered. Each repository will start a job to run. If the project does not exist on the remote registry, a new project will be created automatically, but if it already exists and the user configured in policy has no write privilege to it, the process will fail. The member information will not be replicated.
@ -162,7 +162,7 @@ Entering a keyword in the search field at the top lists all matching projects an
## Administrator options
### Managing user
Administrator can add "Administrator" role to an ordinary user by click button on the left and select "Set as Administrator". To delete a user, select "Delete". Deleting user is only supported under database authentication mode.
Administrator can add "Administrator" role to one or more ordinary users by checking checkboxes and clicking `SET AS ADMINISTRATOR`. To delete users, checked checkboxes and select `DELETE`. Deleting user is only supported under database authentication mode.
![browse project](img/new_set_admin_remove_user.png)

View File

@ -170,7 +170,7 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
//Open the modal now
this.open();
this.forceRefreshView(1000);
this.forceRefreshView(2000);
})
.catch(error => this.errorHandler.error(error));
} else {
@ -208,12 +208,12 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
.then(
response => {
this.inlineAlert.showInlineSuccess({ message: "DESTINATION.TEST_CONNECTION_SUCCESS" });
this.forceRefreshView(1000);
this.forceRefreshView(2000);
this.testOngoing = false;
}).catch(
error => {
this.inlineAlert.showInlineError('DESTINATION.TEST_CONNECTION_FAILURE');
this.forceRefreshView(1000);
this.forceRefreshView(2000);
this.testOngoing = false;
});
}
@ -240,6 +240,7 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
this.reload.emit(true);
this.onGoing = false;
this.close();
this.forceRefreshView(2000);
}).catch(error => {
this.onGoing = false;
let errorMessageKey = this.handleErrorMessageKey(error.status);
@ -248,10 +249,10 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
.subscribe(res => {
this.inlineAlert.showInlineError(res);
});
this.forceRefreshView(1000);
this.forceRefreshView(2000);
}
);
this.forceRefreshView(1000);
}
updateEndpoint() {
@ -285,6 +286,7 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
this.reload.emit(true);
this.close();
this.onGoing = false;
this.forceRefreshView(2000);
})
.catch(
error => {
@ -295,10 +297,10 @@ export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy
this.inlineAlert.showInlineError(res);
});
this.onGoing = false;
this.forceRefreshView(1000);
this.forceRefreshView(2000);
}
);
this.forceRefreshView(1000);
}
handleErrorMessageKey(status: number): string {

View File

@ -16,7 +16,7 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
<clr-dg-row *clrDgItems="let p of changedRules" [clrDgItem]="p" (click)="selectRule(p)" [style.backgroundColor]="(projectScope && withReplicationJob && selectedId === p.id) ? '#eee' : ''">
<clr-dg-cell>{{p.name}}</clr-dg-cell>
<clr-dg-cell *ngIf="!projectScope">
<a href="javascript:void(0)">{{p.projects?.length>0 ? p.projects[0].name : ''}}</a>
<a href="javascript:void(0)" (click)="redirectTo(p)">{{p.projects?.length>0 ? p.projects[0].name : ''}}</a>
</clr-dg-cell>
<clr-dg-cell>{{p.description ? p.description : '-'}}</clr-dg-cell>
<clr-dg-cell>{{p.targets?.length>0 ? p.targets[0].name : ''}}</clr-dg-cell>

View File

@ -66,6 +66,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
@Output() selectOne = new EventEmitter<ReplicationRule>();
@Output() editOne = new EventEmitter<ReplicationRule>();
@Output() toggleOne = new EventEmitter<ReplicationRule>();
@Output() hideJobs = new EventEmitter<any>();
@Output() redirect = new EventEmitter<ReplicationRule>();
@Output() openNewRule = new EventEmitter<any>();
@Output() replicateManual = new EventEmitter<ReplicationRule[]>();
@ -132,6 +133,8 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
if (this.rules && this.rules.length > 0) {
this.selectedId = this.rules[0].id || '';
this.selectOne.emit(this.rules[0]);
} else {
this.hideJobs.emit();
}
this.changedRules = this.rules;
this.selectedRow = this.changedRules[0];

View File

@ -11,7 +11,7 @@ export const REPLICATION_TEMPLATE: string = `
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<hbr-list-replication-rule #listReplicationRule [readonly]="readonly" [projectId]="projectId" [isSystemAdmin]="isSystemAdmin" (replicateManual)=replicateManualRule($event) (selectOne)="selectOneRule($event)" (openNewRule)="openModal()" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
<hbr-list-replication-rule #listReplicationRule [readonly]="readonly" [projectId]="projectId" [isSystemAdmin]="isSystemAdmin" (replicateManual)=replicateManualRule($event) (selectOne)="selectOneRule($event)" (hideJobs)="hideJobs()" (openNewRule)="openModal()" (editOne)="openEditRule($event)" (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" style="padding-left:0px;">
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">

View File

@ -352,6 +352,10 @@ export class ReplicationComponent implements OnInit, OnDestroy {
this.loadFirstPage();
}
hideJobs() {
this.search.ruleId = 0;
}
stopJobs() {
if (this.jobs && this.jobs.length) {
this.isStopOnGoing = true;

View File

@ -39,7 +39,7 @@ export const TAG_TEMPLATE = `
<clr-dg-column style="width: 150px;"[clrDgSortBy]="createdComparator">{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
<clr-dg-column style="width: 140px;" [clrDgField]="'docker_version'" *ngIf="!withClair">{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'TAG.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let t of tags" [clrDgItem]='t'>
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
<clr-dg-cell class="truncated" style="width: 160px;" [ngSwitch]="withClair">
<a *ngSwitchCase="true" href="javascript:void(0)" (click)="onTagClick(t)" title="{{t.name}}">{{t.name}}</a>
<span *ngSwitchDefault>{{t.name}}</span>

View File

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

View File

@ -31,7 +31,7 @@
<clr-datagrid [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="SelectedChange()">
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let m of members" [clrDgItem]="m">
<clr-dg-row *clrDgItems="let m of members" [clrDgItem]="m">
<clr-dg-cell>{{m.username}}</clr-dg-cell>
<clr-dg-cell>
<span *ngIf="ChangeRoleOngoing(m.username)" class="spinner spinner-inline"> Loading... </span>

View File

@ -25,6 +25,7 @@ const FAKE_PASSWORD = 'rjGcfuRu';
styleUrls: ['replication-rule.css']
})
export class ReplicationRuleComponent implements OnInit, OnDestroy {
_localTime: Date = new Date();
policyId: number;
@ -33,7 +34,7 @@ export class ReplicationRuleComponent implements OnInit, OnDestroy {
isFilterHide: boolean = false;
weeklySchedule: boolean;
isScheduleOpt: boolean;
isImmediate: boolean = true;
isImmediate: boolean = false;
noProjectInfo: string = "";
noSelectedProject: boolean = true;
noSelectedEndpoint: boolean = true;
@ -59,6 +60,8 @@ export class ReplicationRuleComponent implements OnInit, OnDestroy {
ruleForm: FormGroup;
copyUpdateForm: ReplicationRule;
emptyEndpoint = new Target();
@ViewChild(ListProjectModelComponent)
projectListModel: ListProjectModelComponent;
@ -91,7 +94,6 @@ export class ReplicationRuleComponent implements OnInit, OnDestroy {
private confirmService: ConfirmationDialogService,
public ref: ChangeDetectorRef) {
this.createForm();
Promise.all([this.repService.getEndpoints(), this.repService.listProjects()])
.then(res => {
if (!res[0]) {
@ -99,6 +101,7 @@ export class ReplicationRuleComponent implements OnInit, OnDestroy {
}else {
this.targetList = res[0];
if (!this.policyId) {
res[0].unshift(this.emptyEndpoint);
this.setTarget([res[0][0]]);
this.realEndpointData.userName = res[0][0].username;
this.realEndpointData.password = FAKE_PASSWORD;
@ -270,6 +273,10 @@ export class ReplicationRuleComponent implements OnInit, OnDestroy {
targetChange($event: any) {
if ($event && $event.target && event.target['value']) {
if ($event.target['value'] === '-1') {
this.noSelectedEndpoint = true;
return;
}
let selecedTarget: Target = this.targetList.find(target => target.id === +$event.target['value']);
this.setTarget([selecedTarget]);
this.noSelectedEndpoint = false;
@ -437,10 +444,9 @@ export class ReplicationRuleComponent implements OnInit, OnDestroy {
}
public hasFormChange(): boolean {
return !isEmptyObject(this.getChanges());
return !isEmptyObject(this.getChanges()) || !compareValue(this.firstEndpointData, this.realEndpointData);
}
onSubmit() {
this.inProgress = true;
let endpointId: string | number = this.ruleForm.value.targets[0].id;

View File

@ -37,8 +37,7 @@ label:first-child {
.projectInput{float: left;}
.projectInput input{background-color: white;}
.switchIcon{width:20px;height:20px; margin-top: 16px;margin-left: 10px;}
.switchIcon{width:20px;height:20px; margin-top: 10px;margin-left: 10px; cursor: pointer;}
.addEndpoint{ margin-top: .25em !important;}
.shadow{position: absolute;top: 8px;}
.shadow1{width:270px; height: 24px;background-color: #fafafa; z-index: 10; top:5px;}
.hoverBg:hover{display: none;}
.is-solid{cursor: pointer;}

View File

@ -57,15 +57,14 @@
<label class="col-md-4 form-group-label-override">{{'DESTINATION.ENDPOINT' | translate}} <span class="colorRed">*</span></label>
<div formArrayName="targets">
<div class="select endpointSelect pull-left" *ngFor="let target of targets.controls; let i= index" [formGroupName]="i">
<select id="ruleTarget " class="inputWidth" (mouseenter)="noSelectedEndpoint= false" (change)="targetChange($event)" formControlName="id">
<option *ngFor="let target of targetList" value="{{target.id}}">{{target.name}}: {{target.endpoint}}</option>
<select id="ruleTarget " class="inputWidth" (change)="targetChange($event)" formControlName="id">
<option *ngFor="let target of targetList" value="{{target.id}}">{{target.name}}<span [hidden]="!target.name">:</span> {{target.endpoint}}</option>
</select>
</div>
<button class="btn btn-info btn-sm addEndpoint" (click)="openModal()"><clr-icon shape="plus"></clr-icon>&nbsp;{{'REPLICATION.NEW' | translate}}</button>
<div [hidden]="noSelectedEndpoint">userName: &nbsp;&nbsp;<input type="text" [(ngModel)]="realEndpointData.userName" [ngModelOptions]="{standalone:true}"></div>
<div [hidden]="noSelectedEndpoint">password: &nbsp;&nbsp;<input type="password" [(ngModel)]="realEndpointData.password" [ngModelOptions]="{standalone:true}"></div>
</div>
<div class="shadow shadow1 hoverBg" #shadowDiv [hidden]="!noSelectedEndpoint || !targetList.length"></div>
</div>
<!--Trigger-->