Merge pull request #4172 from pengpengshui/batchDelection
Modify replication list unsave when modify username or possword
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 31 KiB |
@ -102,7 +102,7 @@ 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)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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];
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;}
|
@ -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> {{'REPLICATION.NEW' | translate}}</button>
|
||||
<div [hidden]="noSelectedEndpoint">userName: <input type="text" [(ngModel)]="realEndpointData.userName" [ngModelOptions]="{standalone:true}"></div>
|
||||
<div [hidden]="noSelectedEndpoint">password: <input type="password" [(ngModel)]="realEndpointData.password" [ngModelOptions]="{standalone:true}"></div>
|
||||
</div>
|
||||
<div class="shadow shadow1 hoverBg" #shadowDiv [hidden]="!noSelectedEndpoint || !targetList.length"></div>
|
||||
</div>
|
||||
|
||||
<!--Trigger-->
|
||||
|