diff --git a/docs/img/log_search_advanced.png b/docs/img/log_search_advanced.png index c0ac422ee..bd04f724d 100644 Binary files a/docs/img/log_search_advanced.png and b/docs/img/log_search_advanced.png differ diff --git a/docs/img/new_add_member.png b/docs/img/new_add_member.png index ed014883c..fc034b261 100644 Binary files a/docs/img/new_add_member.png and b/docs/img/new_add_member.png differ diff --git a/docs/img/new_browse_project.png b/docs/img/new_browse_project.png index cc876780b..4cd55bd49 100644 Binary files a/docs/img/new_browse_project.png and b/docs/img/new_browse_project.png differ diff --git a/docs/img/new_config_email.png b/docs/img/new_config_email.png index 686e3cd56..996694f89 100644 Binary files a/docs/img/new_config_email.png and b/docs/img/new_config_email.png differ diff --git a/docs/img/new_delete_repo.png b/docs/img/new_delete_repo.png index fc6f9b8c8..84edda4d2 100644 Binary files a/docs/img/new_delete_repo.png and b/docs/img/new_delete_repo.png differ diff --git a/docs/img/new_delete_tag.png b/docs/img/new_delete_tag.png index f34141901..be28c034c 100644 Binary files a/docs/img/new_delete_tag.png and b/docs/img/new_delete_tag.png differ diff --git a/docs/img/new_project_log.png b/docs/img/new_project_log.png index 67f27282c..c96ffc6f2 100644 Binary files a/docs/img/new_project_log.png and b/docs/img/new_project_log.png differ diff --git a/docs/img/new_remove_update_member.png b/docs/img/new_remove_update_member.png index 3650ed8c1..16664e761 100644 Binary files a/docs/img/new_remove_update_member.png and b/docs/img/new_remove_update_member.png differ diff --git a/docs/img/new_set_admin_remove_user.png b/docs/img/new_set_admin_remove_user.png index b2c77eed9..1495a2fa7 100644 Binary files a/docs/img/new_set_admin_remove_user.png and b/docs/img/new_set_admin_remove_user.png differ diff --git a/docs/user_guide.md b/docs/user_guide.md index 615db96f0..77195c0a2 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -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) diff --git a/src/ui_ng/lib/src/create-edit-endpoint/create-edit-endpoint.component.ts b/src/ui_ng/lib/src/create-edit-endpoint/create-edit-endpoint.component.ts index b10c24cb0..de2e41302 100644 --- a/src/ui_ng/lib/src/create-edit-endpoint/create-edit-endpoint.component.ts +++ b/src/ui_ng/lib/src/create-edit-endpoint/create-edit-endpoint.component.ts @@ -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 { diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts index c9e3d4312..979fcee1d 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.html.ts @@ -16,7 +16,7 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = ` {{p.name}} - {{p.projects?.length>0 ? p.projects[0].name : ''}} + {{p.projects?.length>0 ? p.projects[0].name : ''}} {{p.description ? p.description : '-'}} {{p.targets?.length>0 ? p.targets[0].name : ''}} diff --git a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts index 135a843f9..bfa68e05e 100644 --- a/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts +++ b/src/ui_ng/lib/src/list-replication-rule/list-replication-rule.component.ts @@ -66,6 +66,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { @Output() selectOne = new EventEmitter(); @Output() editOne = new EventEmitter(); @Output() toggleOne = new EventEmitter(); + @Output() hideJobs = new EventEmitter(); @Output() redirect = new EventEmitter(); @Output() openNewRule = new EventEmitter(); @Output() replicateManual = new EventEmitter(); @@ -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]; diff --git a/src/ui_ng/lib/src/replication/replication.component.html.ts b/src/ui_ng/lib/src/replication/replication.component.html.ts index cc6f68274..d396fffb4 100644 --- a/src/ui_ng/lib/src/replication/replication.component.html.ts +++ b/src/ui_ng/lib/src/replication/replication.component.html.ts @@ -11,7 +11,7 @@ export const REPLICATION_TEMPLATE: string = `
- +
diff --git a/src/ui_ng/lib/src/replication/replication.component.ts b/src/ui_ng/lib/src/replication/replication.component.ts index e0a677bc4..287288391 100644 --- a/src/ui_ng/lib/src/replication/replication.component.ts +++ b/src/ui_ng/lib/src/replication/replication.component.ts @@ -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; diff --git a/src/ui_ng/lib/src/tag/tag.component.html.ts b/src/ui_ng/lib/src/tag/tag.component.html.ts index d621e603a..ecedbd9e3 100644 --- a/src/ui_ng/lib/src/tag/tag.component.html.ts +++ b/src/ui_ng/lib/src/tag/tag.component.html.ts @@ -39,7 +39,7 @@ export const TAG_TEMPLATE = ` {{'REPOSITORY.CREATED' | translate}} {{'REPOSITORY.DOCKER_VERSION' | translate}} {{'TAG.PLACEHOLDER' | translate }} - + {{t.name}} {{t.name}} diff --git a/src/ui_ng/package.json b/src/ui_ng/package.json index 03bb05a35..e356c01e6 100644 --- a/src/ui_ng/package.json +++ b/src/ui_ng/package.json @@ -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", diff --git a/src/ui_ng/src/app/project/member/member.component.html b/src/ui_ng/src/app/project/member/member.component.html index 0bc257ee2..a47b8264d 100644 --- a/src/ui_ng/src/app/project/member/member.component.html +++ b/src/ui_ng/src/app/project/member/member.component.html @@ -31,7 +31,7 @@ {{'MEMBER.NAME' | translate}} {{'MEMBER.ROLE' | translate}} - + {{m.username}} Loading... diff --git a/src/ui_ng/src/app/replication/replication-rule/replication-rule.component.ts b/src/ui_ng/src/app/replication/replication-rule/replication-rule.component.ts index 6f787d910..311316cab 100644 --- a/src/ui_ng/src/app/replication/replication-rule/replication-rule.component.ts +++ b/src/ui_ng/src/app/replication/replication-rule/replication-rule.component.ts @@ -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; diff --git a/src/ui_ng/src/app/replication/replication-rule/replication-rule.css b/src/ui_ng/src/app/replication/replication-rule/replication-rule.css index 2ab6f4dea..4942247e5 100644 --- a/src/ui_ng/src/app/replication/replication-rule/replication-rule.css +++ b/src/ui_ng/src/app/replication/replication-rule/replication-rule.css @@ -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;} \ No newline at end of file +.is-solid{cursor: pointer;} \ No newline at end of file diff --git a/src/ui_ng/src/app/replication/replication-rule/replication-rule.html b/src/ui_ng/src/app/replication/replication-rule/replication-rule.html index 6dcd4f072..b0e1105b8 100644 --- a/src/ui_ng/src/app/replication/replication-rule/replication-rule.html +++ b/src/ui_ng/src/app/replication/replication-rule/replication-rule.html @@ -57,15 +57,14 @@
- +
userName:   
password:   
-