-
-
-
-
-
-
-
-
-
-
-
- {{'MEMBER.NAME' | translate}}
- {{'MEMBER.ROLE' | translate}}
- {{'MEMBER.ACTIONS' | translate}}
-
- {{u.username}}
- {{roleInfo[u.role_id] | translate}}
-
-
-
-
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/member/member.component.ts b/src/ui_ng/src/app/project/member/member.component.ts
index c2319be27..2b5201868 100644
--- a/src/ui_ng/src/app/project/member/member.component.ts
+++ b/src/ui_ng/src/app/project/member/member.component.ts
@@ -9,11 +9,11 @@ import { MemberService } from './member.service';
import { AddMemberComponent } from './add-member/add-member.component';
import { MessageService } from '../../global-message/message.service';
-import { AlertType } from '../../shared/shared.const';
+import { AlertType, DeletionTargets } from '../../shared/shared.const';
import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
-
+import { SessionService } from '../../shared/session.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
@@ -21,13 +21,13 @@ import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
-export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST'};
+export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };
@Component({
templateUrl: 'member.component.html'
})
export class MemberComponent implements OnInit {
-
+
currentUser: SessionUser;
members: Member[];
projectId: number;
@@ -36,34 +36,37 @@ export class MemberComponent implements OnInit {
@ViewChild(AddMemberComponent)
addMemberComponent: AddMemberComponent;
- constructor(private route: ActivatedRoute, private router: Router,
- private memberService: MemberService, private messageService: MessageService,
- private deletionDialogService: DeletionDialogService) {
+ constructor(private route: ActivatedRoute, private router: Router,
+ private memberService: MemberService, private messageService: MessageService,
+ private deletionDialogService: DeletionDialogService,
+ session:SessionService) {
//Get current user from registered resolver.
- this.route.data.subscribe(data=>this.currentUser =
data['memberResolver']);
- deletionDialogService.deletionConfirm$.subscribe(userId=>{
- this.memberService
- .deleteMember(this.projectId, userId)
- .subscribe(
- response=>{
- console.log('Successful change role with user ' + userId);
+ this.currentUser = session.getCurrentUser();
+ deletionDialogService.deletionConfirm$.subscribe(message => {
+ if (message && message.targetId === DeletionTargets.PROJECT_MEMBER) {
+ this.memberService
+ .deleteMember(this.projectId, message.data)
+ .subscribe(
+ response => {
+ console.log('Successful change role with user ' + message.data);
this.retrieve(this.projectId, '');
},
- error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId, AlertType.DANGER)
- );
- })
+ error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + message.data, AlertType.DANGER)
+ );
+ }
+ });
}
- retrieve(projectId:number, username: string) {
+ retrieve(projectId: number, username: string) {
this.memberService
- .listMembers(projectId, username)
- .subscribe(
- response=>this.members = response,
- error=>{
- this.router.navigate(['/harbor', 'projects']);
- this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
- }
- );
+ .listMembers(projectId, username)
+ .subscribe(
+ response => this.members = response,
+ error => {
+ this.router.navigate(['/harbor', 'projects']);
+ this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
+ }
+ );
}
ngOnInit() {
@@ -77,29 +80,39 @@ export class MemberComponent implements OnInit {
openAddMemberModal() {
this.addMemberComponent.openAddMemberModal();
}
-
+
addedMember() {
this.retrieve(this.projectId, '');
}
changeRole(userId: number, roleId: number) {
this.memberService
- .changeMemberRole(this.projectId, userId, roleId)
- .subscribe(
- response=>{
- console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
- this.retrieve(this.projectId, '');
- },
- error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER)
- );
+ .changeMemberRole(this.projectId, userId, roleId)
+ .subscribe(
+ response => {
+ console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
+ this.retrieve(this.projectId, '');
+ },
+ error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER)
+ );
}
deleteMember(userId: number) {
- let deletionMessage: DeletionMessage = new DeletionMessage('Delete Member', 'Confirm to delete this member?', userId);
+ let deletionMessage: DeletionMessage = new DeletionMessage(
+ 'MEMBER.DELETION_TITLE',
+ 'MEMBER.DELETION_SUMMARY',
+ userId+"",
+ userId,
+ DeletionTargets.PROJECT_MEMBER
+ );
this.deletionDialogService.openComfirmDialog(deletionMessage);
}
-
+
doSearch(searchMember) {
this.retrieve(this.projectId, searchMember);
}
+
+ refresh() {
+ this.retrieve(this.projectId, '');
+ }
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/member/member.service.ts b/src/ui_ng/src/app/project/member/member.service.ts
index e4897f23d..5578b1083 100644
--- a/src/ui_ng/src/app/project/member/member.service.ts
+++ b/src/ui_ng/src/app/project/member/member.service.ts
@@ -9,8 +9,6 @@ import 'rxjs/add/observable/throw';
import { BaseService } from '../../service/base.service';
import { Member } from './member';
-export const urlPrefix = '';
-
@Injectable()
export class MemberService extends BaseService {
@@ -21,7 +19,7 @@ export class MemberService extends BaseService {
listMembers(projectId: number, username: string): Observable {
console.log('Get member from project_id:' + projectId + ', username:' + username);
return this.http
- .get(urlPrefix + `/api/projects/${projectId}/members?username=${username}`)
+ .get(`/api/projects/${projectId}/members?username=${username}`)
.map(response=>response.json())
.catch(error=>this.handleError(error));
}
@@ -29,7 +27,7 @@ export class MemberService extends BaseService {
addMember(projectId: number, username: string, roleId: number): Observable {
console.log('Adding member with username:' + username + ', roleId:' + roleId + ' under projectId:' + projectId);
return this.http
- .post(urlPrefix + `/api/projects/${projectId}/members`, { username: username, roles: [ roleId ] })
+ .post(`/api/projects/${projectId}/members`, { username: username, roles: [ roleId ] })
.map(response=>response.status)
.catch(error=>Observable.throw(error));
}
@@ -37,7 +35,7 @@ export class MemberService extends BaseService {
changeMemberRole(projectId: number, userId: number, roleId: number): Observable {
console.log('Changing member role with userId:' + ' to roleId:' + roleId + ' under projectId:' + projectId);
return this.http
- .put(urlPrefix + `/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]})
+ .put(`/api/projects/${projectId}/members/${userId}`, { roles: [ roleId ]})
.map(response=>response.status)
.catch(error=>Observable.throw(error));
}
@@ -45,7 +43,7 @@ export class MemberService extends BaseService {
deleteMember(projectId: number, userId: number): Observable {
console.log('Deleting member role with userId:' + userId + ' under projectId:' + projectId);
return this.http
- .delete(urlPrefix + `/api/projects/${projectId}/members/${userId}`)
+ .delete(`/api/projects/${projectId}/members/${userId}`)
.map(response=>response.status)
.catch(error=>Observable.throw(error));
}
diff --git a/src/ui_ng/src/app/project/project-routing.module.ts b/src/ui_ng/src/app/project/project-routing.module.ts
deleted file mode 100644
index cbb60541c..000000000
--- a/src/ui_ng/src/app/project/project-routing.module.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-
-import { HarborShellComponent } from '../base/harbor-shell/harbor-shell.component';
-import { ProjectComponent } from './project.component';
-import { ProjectDetailComponent } from './project-detail/project-detail.component';
-
-import { RepositoryComponent } from '../repository/repository.component';
-import { ReplicationComponent } from '../replication/replication.component';
-import { MemberComponent } from './member/member.component';
-import { AuditLogComponent } from '../log/audit-log.component';
-
-import { BaseRoutingResolver } from '../base/base-routing-resolver.service';
-import { ProjectRoutingResolver } from './project-routing-resolver.service';
-
-const projectRoutes: Routes = [
- {
- path: 'harbor',
- component: HarborShellComponent,
- resolve: {
- harborResolver: BaseRoutingResolver
- },
- children: [
- {
- path: 'projects',
- component: ProjectComponent,
- resolve: {
- projectsResolver: BaseRoutingResolver
- }
- },
- {
- path: 'projects/:id',
- component: ProjectDetailComponent,
- resolve: {
- projectResolver: ProjectRoutingResolver
- },
- children: [
- { path: 'repository', component: RepositoryComponent },
- {
- path: 'replication', component: ReplicationComponent,
- resolve: {
- replicationResolver: BaseRoutingResolver
- }
- },
- {
- path: 'member', component: MemberComponent,
- resolve: {
- memberResolver: BaseRoutingResolver
- }
- },
- {
- path: 'log', component: AuditLogComponent,
- resolve: {
- auditLogResolver: BaseRoutingResolver
- }
- }
- ]
- }
- ]
- }
-];
-
-@NgModule({
- imports: [
- RouterModule.forChild(projectRoutes)
- ],
- exports: [RouterModule]
-})
-export class ProjectRoutingModule { }
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/project.component.html b/src/ui_ng/src/app/project/project.component.html
index 93a3be1e1..0f7b929d6 100644
--- a/src/ui_ng/src/app/project/project.component.html
+++ b/src/ui_ng/src/app/project/project.component.html
@@ -4,7 +4,7 @@
-
diff --git a/src/ui_ng/src/app/project/project.component.ts b/src/ui_ng/src/app/project/project.component.ts
index f3ebee0c5..8565c5f22 100644
--- a/src/ui_ng/src/app/project/project.component.ts
+++ b/src/ui_ng/src/app/project/project.component.ts
@@ -12,11 +12,18 @@ import { ListProjectComponent } from './list-project/list-project.component';
import { MessageService } from '../global-message/message.service';
import { Message } from '../global-message/message';
-export const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS'};
-
import { AlertType } from '../shared/shared.const';
import { Response } from '@angular/http';
+import { DeletionDialogService } from '../shared/deletion-dialog/deletion-dialog.service';
+import { DeletionMessage } from '../shared/deletion-dialog/deletion-message';
+import { DeletionTargets } from '../shared/shared.const';
+
+import { Subscription } from 'rxjs/Subscription';
+
+
+const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS'};
+
@Component({
selector: 'project',
templateUrl: 'project.component.html',
@@ -37,7 +44,27 @@ export class ProjectComponent implements OnInit {
currentFilteredType: number = 0;
lastFilteredType: number = 0;
- constructor(private projectService: ProjectService, private messageService: MessageService){}
+ subscription: Subscription;
+
+ constructor(
+ private projectService: ProjectService,
+ private messageService: MessageService,
+ private deletionDialogService: DeletionDialogService){
+ this.subscription = deletionDialogService.deletionConfirm$.subscribe(message => {
+ if (message && message.targetId === DeletionTargets.PROJECT) {
+ let projectId = message.data;
+ this.projectService
+ .deleteProject(projectId)
+ .subscribe(
+ response=>{
+ console.log('Successful delete project with ID:' + projectId);
+ this.retrieve('', this.lastFilteredType);
+ },
+ error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
+ );
+ }
+ });
+ }
ngOnInit(): void {
this.retrieve('', this.lastFilteredType);
@@ -75,24 +102,30 @@ export class ProjectComponent implements OnInit {
}
toggleProject(p: Project) {
- this.projectService
+ if (p) {
+ p.public === 0 ? p.public = 1 : p.public = 0;
+ this.projectService
.toggleProjectPublic(p.project_id, p.public)
.subscribe(
response=>console.log('Successful toggled project_id:' + p.project_id),
error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
);
+ }
}
deleteProject(p: Project) {
- this.projectService
- .deleteProject(p.project_id)
- .subscribe(
- response=>{
- console.log('Successful delete project_id:' + p.project_id);
- this.retrieve('', this.lastFilteredType);
- },
- error=>this.messageService.announceMessage(error.status, error, AlertType.WARNING)
- );
+ let deletionMessage = new DeletionMessage(
+ 'PROJECT.DELETION_TITLE',
+ 'PROJECT.DELETION_SUMMARY',
+ p.name,
+ p.project_id,
+ DeletionTargets.PROJECT
+ );
+ this.deletionDialogService.openComfirmDialog(deletionMessage);
+ }
+
+ refresh(): void {
+ this.retrieve('', this.lastFilteredType);
}
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/project.css b/src/ui_ng/src/app/project/project.css
index 2613a0fc3..e69de29bb 100644
--- a/src/ui_ng/src/app/project/project.css
+++ b/src/ui_ng/src/app/project/project.css
@@ -1,3 +0,0 @@
-.my-project-pull-right {
- float: right;
-}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/project.module.ts b/src/ui_ng/src/app/project/project.module.ts
index 4d4eef544..d23555de6 100644
--- a/src/ui_ng/src/app/project/project.module.ts
+++ b/src/ui_ng/src/app/project/project.module.ts
@@ -1,5 +1,6 @@
import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared/shared.module';
import { RepositoryModule } from '../repository/repository.module';
import { ReplicationModule } from '../replication/replication.module';
@@ -7,40 +8,35 @@ import { LogModule } from '../log/log.module';
import { ProjectComponent } from './project.component';
import { CreateProjectComponent } from './create-project/create-project.component';
-import { ActionProjectComponent } from './action-project/action-project.component';
import { ListProjectComponent } from './list-project/list-project.component';
import { ProjectDetailComponent } from './project-detail/project-detail.component';
-
import { MemberComponent } from './member/member.component';
import { AddMemberComponent } from './member/add-member/add-member.component';
-import { ProjectRoutingModule } from './project-routing.module';
-
import { ProjectService } from './project.service';
import { MemberService } from './member/member.service';
import { ProjectRoutingResolver } from './project-routing-resolver.service';
@NgModule({
- imports: [
+ imports: [
SharedModule,
RepositoryModule,
ReplicationModule,
LogModule,
- ProjectRoutingModule
+ RouterModule
],
- declarations: [
+ declarations: [
ProjectComponent,
CreateProjectComponent,
- ActionProjectComponent,
ListProjectComponent,
ProjectDetailComponent,
MemberComponent,
AddMemberComponent
],
- exports: [ ProjectComponent ],
- providers: [ ProjectRoutingResolver, ProjectService, MemberService ]
+ exports: [ProjectComponent],
+ providers: [ProjectRoutingResolver, ProjectService, MemberService]
})
export class ProjectModule {
-
+
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/project.service.ts b/src/ui_ng/src/app/project/project.service.ts
index b560c096e..77ae7c004 100644
--- a/src/ui_ng/src/app/project/project.service.ts
+++ b/src/ui_ng/src/app/project/project.service.ts
@@ -12,8 +12,6 @@ import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
-const url_prefix = '';
-
@Injectable()
export class ProjectService {
@@ -24,7 +22,7 @@ export class ProjectService {
getProject(projectId: number): Promise
{
return this.http
- .get(url_prefix + `/api/projects/${projectId}`)
+ .get(`/api/projects/${projectId}`)
.toPromise()
.then(response=>response.json() as Project)
.catch(error=>Observable.throw(error));
@@ -32,14 +30,14 @@ export class ProjectService {
listProjects(name: string, isPublic: number): Observable{
return this.http
- .get(url_prefix + `/api/projects?project_name=${name}&is_public=${isPublic}`, this.options)
+ .get(`/api/projects?project_name=${name}&is_public=${isPublic}`, this.options)
.map(response=>response.json())
.catch(error=>Observable.throw(error));
}
createProject(name: string, isPublic: number): Observable {
return this.http
- .post(url_prefix + `/api/projects`,
+ .post(`/api/projects`,
JSON.stringify({'project_name': name, 'public': isPublic})
, this.options)
.map(response=>response.status)
@@ -48,14 +46,14 @@ export class ProjectService {
toggleProjectPublic(projectId: number, isPublic: number): Observable {
return this.http
- .put(url_prefix + `/api/projects/${projectId}/publicity`, { 'public': isPublic }, this.options)
+ .put(`/api/projects/${projectId}/publicity`, { 'public': isPublic }, this.options)
.map(response=>response.status)
.catch(error=>Observable.throw(error));
}
deleteProject(projectId: number): Observable {
return this.http
- .delete(url_prefix + `/api/projects/${projectId}`)
+ .delete(`/api/projects/${projectId}`)
.map(response=>response.status)
.catch(error=>Observable.throw(error));
}
diff --git a/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.html b/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.html
new file mode 100644
index 000000000..79dc56317
--- /dev/null
+++ b/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.html
@@ -0,0 +1,52 @@
+
+ New Endpoint
+
+
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts b/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts
new file mode 100644
index 000000000..e68f329cb
--- /dev/null
+++ b/src/ui_ng/src/app/replication/create-edit-destination/create-edit-destination.component.ts
@@ -0,0 +1,128 @@
+import { Component, Output, EventEmitter } from '@angular/core';
+
+import { ReplicationService } from '../replication.service';
+import { MessageService } from '../../global-message/message.service';
+import { AlertType, ActionType } from '../../shared/shared.const';
+
+import { Target } from '../target';
+
+
+@Component({
+ selector: 'create-edit-destination',
+ templateUrl: './create-edit-destination.component.html'
+})
+export class CreateEditDestinationComponent {
+
+ createEditDestinationOpened: boolean;
+
+ errorMessageOpened: boolean;
+ errorMessage: string;
+
+ testOngoing: boolean;
+ pingTestMessage: string;
+ pingStatus: boolean;
+
+ actionType: ActionType;
+
+ target: Target = new Target();
+
+ @Output() reload = new EventEmitter();
+
+ constructor(
+ private replicationService: ReplicationService,
+ private messageService: MessageService) {}
+
+ openCreateEditTarget(targetId?: number) {
+ this.target = new Target();
+
+ this.createEditDestinationOpened = true;
+
+ this.errorMessageOpened = false;
+ this.errorMessage = '';
+
+ this.pingTestMessage = '';
+ this.pingStatus = true;
+ this.testOngoing = false;
+
+ if(targetId) {
+ this.actionType = ActionType.EDIT;
+ this.replicationService
+ .getTarget(targetId)
+ .subscribe(
+ target=>this.target=target,
+ error=>this.messageService
+ .announceMessage(error.status, 'Failed to get target with ID:' + targetId, AlertType.DANGER)
+ );
+ } else {
+ this.actionType = ActionType.ADD_NEW;
+ }
+ }
+
+ testConnection() {
+ this.pingTestMessage = 'Testing connection...';
+ this.pingStatus = true;
+ this.testOngoing = !this.testOngoing;
+ this.replicationService
+ .pingTarget(this.target)
+ .subscribe(
+ response=>{
+ this.pingStatus = true;
+ this.pingTestMessage = 'Connection tested successfully.';
+ this.testOngoing = !this.testOngoing;
+ },
+ error=>{
+ this.pingStatus = false;
+ this.pingTestMessage = 'Failed to ping target.';
+ this.testOngoing = !this.testOngoing;
+ }
+ )
+ }
+
+ onSubmit() {
+ this.errorMessage = '';
+ this.errorMessageOpened = false;
+
+ switch(this.actionType) {
+ case ActionType.ADD_NEW:
+ this.replicationService
+ .createTarget(this.target)
+ .subscribe(
+ response=>{
+ console.log('Successful added target.');
+ this.createEditDestinationOpened = false;
+ this.reload.emit(true);
+ },
+ error=>{
+ this.errorMessageOpened = true;
+ this.errorMessage = 'Failed to add target:' + error;
+ this.messageService
+ .announceMessage(error.status, this.errorMessage, AlertType.DANGER);
+ }
+ );
+ break;
+ case ActionType.EDIT:
+ this.replicationService
+ .updateTarget(this.target)
+ .subscribe(
+ response=>{
+ console.log('Successful updated target.');
+ this.createEditDestinationOpened = false;
+ this.reload.emit(true);
+ },
+ error=>{
+ this.errorMessageOpened = true;
+ this.errorMessage = 'Failed to update target:' + error;
+ this.messageService
+ .announceMessage(error.status, this.errorMessage, AlertType.DANGER);
+ }
+ );
+ break;
+ }
+ }
+
+ onErrorMessageClose(): void {
+ this.errorMessageOpened = false;
+ this.errorMessage = '';
+ }
+
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/create-edit-policy/create-edit-policy.component.html b/src/ui_ng/src/app/replication/create-edit-policy/create-edit-policy.component.html
deleted file mode 100644
index 37a54579c..000000000
--- a/src/ui_ng/src/app/replication/create-edit-policy/create-edit-policy.component.html
+++ /dev/null
@@ -1,54 +0,0 @@
-
- Add Policy
-
-
-
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/create-edit-policy/create-edit-policy.component.ts b/src/ui_ng/src/app/replication/create-edit-policy/create-edit-policy.component.ts
deleted file mode 100644
index 3e9ed5dc9..000000000
--- a/src/ui_ng/src/app/replication/create-edit-policy/create-edit-policy.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Component, Input } from '@angular/core';
-
-import { ReplicationService } from '../replication.service';
-
-@Component({
- selector: 'create-edit-policy',
- templateUrl: 'create-edit-policy.component.html'
-})
-export class CreateEditPolicyComponent {
-
- createEditPolicyOpened: boolean;
-
- constructor(private replicationService: ReplicationService) {}
-
- openCreateEditPolicy(): void {
- console.log('createEditPolicyOpened:' + this.createEditPolicyOpened);
- this.createEditPolicyOpened = true;
- }
-}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/destination/destination.component.html b/src/ui_ng/src/app/replication/destination/destination.component.html
new file mode 100644
index 000000000..5a069f971
--- /dev/null
+++ b/src/ui_ng/src/app/replication/destination/destination.component.html
@@ -0,0 +1,30 @@
+
+
+
+
+ Name
+ Destination
+ Creation Time
+
+ {{t.name}}
+ {{t.endpoint}}
+ {{t.creation_time}}
+
+ Edit Target
+ Delete
+
+
+
+ {{ (targets ? targets.length : 0) }} item(s)
+
+
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/destination/destination.component.ts b/src/ui_ng/src/app/replication/destination/destination.component.ts
new file mode 100644
index 000000000..8078e4dec
--- /dev/null
+++ b/src/ui_ng/src/app/replication/destination/destination.component.ts
@@ -0,0 +1,103 @@
+import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
+import { Target } from '../target';
+import { ReplicationService } from '../replication.service';
+import { MessageService } from '../../global-message/message.service';
+import { AlertType } from '../../shared/shared.const';
+
+import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
+import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
+
+import { DeletionTargets } from '../../shared/shared.const';
+
+import { Subscription } from 'rxjs/Subscription';
+
+import { CreateEditDestinationComponent } from '../create-edit-destination/create-edit-destination.component';
+
+@Component({
+ selector: 'destination',
+ templateUrl: 'destination.component.html'
+})
+export class DestinationComponent implements OnInit {
+
+ @ViewChild(CreateEditDestinationComponent)
+ createEditDestinationComponent: CreateEditDestinationComponent;
+
+ targets: Target[];
+ target: Target;
+
+ targetName: string;
+ subscription : Subscription;
+
+ constructor(
+ private replicationService: ReplicationService,
+ private messageService: MessageService,
+ private deletionDialogService: DeletionDialogService) {
+ this.subscription = this.deletionDialogService.deletionConfirm$.subscribe(message=>{
+ let targetId = message.data;
+ this.replicationService
+ .deleteTarget(targetId)
+ .subscribe(
+ response=>{
+ console.log('Successful deleted target with ID:' + targetId);
+ this.reload();
+ },
+ error=>this.messageService
+ .announceMessage(error.status,
+ 'Failed to delete target with ID:' + targetId + ', error:' + error,
+ AlertType.DANGER)
+ );
+ });
+ }
+
+ ngOnInit(): void {
+ this.targetName = '';
+ this.retrieve('');
+ }
+
+ ngOnDestroy(): void {
+ if(this.subscription) {
+ this.subscription.unsubscribe();
+ }
+ }
+
+ retrieve(targetName: string): void {
+ this.replicationService
+ .listTargets(targetName)
+ .subscribe(
+ targets=>this.targets = targets,
+ error=>this.messageService.announceMessage(error.status,'Failed to get targets:' + error, AlertType.DANGER)
+ );
+ }
+
+ doSearchTargets(targetName: string) {
+ this.targetName = targetName;
+ this.retrieve(targetName);
+ }
+
+ refreshTargets() {
+ this.retrieve('');
+ }
+
+ reload() {
+ this.retrieve(this.targetName);
+ }
+
+ openModal() {
+ this.createEditDestinationComponent.openCreateEditTarget();
+ this.target = new Target();
+ }
+
+ editTarget(target: Target) {
+ if(target) {
+ this.createEditDestinationComponent.openCreateEditTarget(target.id);
+ }
+ }
+
+ deleteTarget(target: Target) {
+ if(target) {
+ let targetId = target.id;
+ let deletionMessage = new DeletionMessage('REPLICATION.DELETION_TITLE_TARGET', 'REPLICATION.DELETION_SUMMARY_TARGET', target.name, target.id, DeletionTargets.TARGET);
+ this.deletionDialogService.openComfirmDialog(deletionMessage);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/list-policy/custom-highlight.directive.ts b/src/ui_ng/src/app/replication/list-policy/custom-highlight.directive.ts
deleted file mode 100644
index 484cf5d5f..000000000
--- a/src/ui_ng/src/app/replication/list-policy/custom-highlight.directive.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Directive, ElementRef, HostListener } from '@angular/core';
-
-export const customColor = 'blue';
-export const customFontColor = 'white';
-
-@Directive({
- selector: '[custom-highlight]'
-})
-export class CustomHighlightDirective {
- constructor(private el: ElementRef) {}
-
- @HostListener('mouseenter')
- onMouseEnter(): void {
- this.el.nativeElement.style.backgroundColor = customColor;
- this.el.nativeElement.style.color = customFontColor;
- }
-
- @HostListener('mouseout')
- onMouseOut(): void {
- this.el.nativeElement.style.backgroundColor = null;
- this.el.nativeElement.style.color = null;
- }
-}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/list-policy/list-policy.component.html b/src/ui_ng/src/app/replication/list-policy/list-policy.component.html
deleted file mode 100644
index 8617f81d3..000000000
--- a/src/ui_ng/src/app/replication/list-policy/list-policy.component.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
- Name
- Description
- Destination
- Last start time
- Activation
- Action
-
- {{p.name}}
- {{p.description}}
- {{p.target_name}}
- {{p.start_time}}
- {{p.enabled}}
-
-
-
-
-
-
-
-
- {{ (policies ? policies.length : 0) }} item(s)
-
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/list-policy/list-policy.component.ts b/src/ui_ng/src/app/replication/list-policy/list-policy.component.ts
deleted file mode 100644
index 486520803..000000000
--- a/src/ui_ng/src/app/replication/list-policy/list-policy.component.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Component, Input, Output, EventEmitter } from '@angular/core';
-
-import { ReplicationService } from '../replication.service';
-import { Policy } from '../policy';
-
-@Component({
- selector: 'list-policy',
- templateUrl: 'list-policy.component.html'
-})
-export class ListPolicyComponent {
-
- @Input() policies: Policy[];
- @Output() selectOne = new EventEmitter();
-
- constructor(private replicationService: ReplicationService){}
-
- selectPolicy(policy: Policy): void {
- console.log('Select policy ID:' + policy.id);
- this.selectOne.emit(policy.id);
- }
-}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/replication-management/replication-management.component.html b/src/ui_ng/src/app/replication/replication-management/replication-management.component.html
new file mode 100644
index 000000000..298a13d2a
--- /dev/null
+++ b/src/ui_ng/src/app/replication/replication-management/replication-management.component.html
@@ -0,0 +1,12 @@
+Replications
+
+
diff --git a/src/ui_ng/src/app/replication/replication-management/replication-management.component.ts b/src/ui_ng/src/app/replication/replication-management/replication-management.component.ts
new file mode 100644
index 000000000..78ef2fe57
--- /dev/null
+++ b/src/ui_ng/src/app/replication/replication-management/replication-management.component.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'replication-management',
+ templateUrl: 'replication-management.component.html',
+ styleUrls: [ 'replication-management.css' ]
+})
+export class ReplicationManagementComponent {}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/replication-management/replication-management.css b/src/ui_ng/src/app/replication/replication-management/replication-management.css
new file mode 100644
index 000000000..eb5e65fee
--- /dev/null
+++ b/src/ui_ng/src/app/replication/replication-management/replication-management.css
@@ -0,0 +1,32 @@
+.custom-h2 {
+ margin-top: 0px !important;
+}
+
+.custom-add-button {
+ font-size: medium;
+ margin-left: -12px;
+}
+
+.filter-icon {
+ position: relative;
+ right: -12px;
+}
+
+.filter-pos {
+ float: right;
+ margin-right: 24px;
+ position: relative;
+ top: 8px;
+}
+
+.action-panel-pos {
+ position: relative;
+ top: 20px;
+}
+
+.refresh-btn {
+ position: absolute;
+ right: -4px;
+ top: 8px;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/replication.component.html b/src/ui_ng/src/app/replication/replication.component.html
index eebb61e40..2c5d8f44d 100644
--- a/src/ui_ng/src/app/replication/replication.component.html
+++ b/src/ui_ng/src/app/replication/replication.component.html
@@ -2,33 +2,47 @@
-
-
+
+
-
-
-
-
-
-
-
- Replication Jobs for 'project01/sync_01'
-
-
+
-
-
+
+
+
+
+
+
+
+ Replication Jobs
+
+
+
{{toggleJobSearchOption[currentJobSearchOption]}}
+
+
+
+
+
diff --git a/src/ui_ng/src/app/replication/replication.component.ts b/src/ui_ng/src/app/replication/replication.component.ts
index ced3b000b..3daae67bb 100644
--- a/src/ui_ng/src/app/replication/replication.component.ts
+++ b/src/ui_ng/src/app/replication/replication.component.ts
@@ -1,11 +1,13 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { CreateEditPolicyComponent } from './create-edit-policy/create-edit-policy.component';
+import { CreateEditPolicyComponent } from '../shared/create-edit-policy/create-edit-policy.component';
import { MessageService } from '../global-message/message.service';
import { AlertType } from '../shared/shared.const';
+import { SessionService } from '../shared/session.service';
+
import { ReplicationService } from './replication.service';
import { SessionUser } from '../shared/session-user';
@@ -13,6 +15,34 @@ import { Policy } from './policy';
import { Job } from './job';
import { Target } from './target';
+const ruleStatus = [
+ { 'key': '', 'description': 'All Status'},
+ { 'key': '1', 'description': 'Enabled'},
+ { 'key': '0', 'description': 'Disabled'}
+];
+
+const jobStatus = [
+ { 'key': '', 'description': 'All' },
+ { 'key': 'pending', 'description': 'Pending' },
+ { 'key': 'running', 'description': 'Running' },
+ { 'key': 'error', 'description': 'Error' },
+ { 'key': 'retrying', 'description': 'Retrying' },
+ { 'key': 'stopped' , 'description': 'Stopped' },
+ { 'key': 'finished', 'description': 'Finished' },
+ { 'key': 'canceled', 'description': 'Canceled' }
+];
+
+const optionalSearch: {} = {0: 'Advanced', 1: 'Simple'};
+
+class SearchOption {
+ policyId: number;
+ policyName: string = '';
+ repoName: string = '';
+ status: string = '';
+ startTime: string = '';
+ endTime: string = '';
+}
+
@Component({
selector: 'replicaton',
templateUrl: 'replication.component.html'
@@ -22,34 +52,55 @@ export class ReplicationComponent implements OnInit {
currentUser: SessionUser;
projectId: number;
- policyName: string;
-
- policy: Policy;
-
+ search: SearchOption;
+
+ ruleStatus = ruleStatus;
+ currentRuleStatus: {key: string, description: string};
+
+ jobStatus = jobStatus;
+ currentJobStatus: {key: string, description: string};
+
changedPolicies: Policy[];
changedJobs: Job[];
- @ViewChild(CreateEditPolicyComponent)
- createEditPolicyComponent: CreateEditPolicyComponent
+ policies: Policy[];
+ jobs: Job[];
- constructor(private route: ActivatedRoute, private messageService: MessageService, private replicationService: ReplicationService) {
- this.route.data.subscribe(data=>this.currentUser =
data);
+ toggleJobSearchOption = optionalSearch;
+ currentJobSearchOption: number;
+
+ @ViewChild(CreateEditPolicyComponent)
+ createEditPolicyComponent: CreateEditPolicyComponent;
+
+ constructor(
+ private sessionService: SessionService,
+ private messageService: MessageService,
+ private replicationService: ReplicationService,
+ private route: ActivatedRoute) {
+ this.currentUser = this.sessionService.getCurrentUser();
}
ngOnInit(): void {
this.projectId = +this.route.snapshot.parent.params['id'];
console.log('Get projectId from route params snapshot:' + this.projectId);
+ this.search = new SearchOption();
+ this.currentRuleStatus = this.ruleStatus[0];
+ this.currentJobStatus = this.jobStatus[0];
+ this.currentJobSearchOption = 0;
this.retrievePolicies();
}
retrievePolicies(): void {
this.replicationService
- .listPolicies(this.projectId, this.policyName)
+ .listPolicies(this.search.policyName, this.projectId)
.subscribe(
response=>{
this.changedPolicies = response;
+ this.policies = this.changedPolicies;
if(this.changedPolicies && this.changedPolicies.length > 0) {
this.fetchPolicyJobs(this.changedPolicies[0].id);
+ } else {
+ this.changedJobs = [];
}
},
error=>this.messageService.announceMessage(error.status,'Failed to get policies with project ID:' + this.projectId, AlertType.DANGER)
@@ -57,17 +108,98 @@ export class ReplicationComponent implements OnInit {
}
openModal(): void {
+ console.log('Open modal to create policy.');
this.createEditPolicyComponent.openCreateEditPolicy();
- console.log('Clicked open create-edit policy.');
}
- fetchPolicyJobs(policyId: number) {
- console.log('Received policy ID ' + policyId + ' by clicked row.');
+ openEditPolicy(policyId: number) {
+ console.log('Open modal to edit policy ID:' + policyId);
+ this.createEditPolicyComponent.openCreateEditPolicy(policyId);
+ }
+
+ fetchPolicyJobs(policyId: number) {
+ this.search.policyId = policyId;
+ console.log('Received policy ID ' + this.search.policyId + ' by clicked row.');
this.replicationService
- .listJobs(policyId)
+ .listJobs(this.search.policyId, this.search.status, this.search.repoName, this.search.startTime, this.search.endTime)
.subscribe(
- response=>this.changedJobs = response,
- error=>this.messageService.announceMessage(error.status, 'Failed to fetch jobs with policy ID:' + policyId, AlertType.DANGER)
+ response=>{
+ this.changedJobs = response;
+ this.jobs = this.changedJobs;
+ },
+ error=>this.messageService.announceMessage(error.status, 'Failed to fetch jobs with policy ID:' + this.search.policyId, AlertType.DANGER)
);
}
+
+ selectOne(policy: Policy) {
+ if(policy) {
+ this.fetchPolicyJobs(policy.id);
+ }
+ }
+
+ doSearchPolicies(policyName: string) {
+ this.search.policyName = policyName;
+ this.retrievePolicies();
+ }
+
+ doFilterPolicyStatus(status: string) {
+ console.log('Do filter policies with status:' + status);
+ this.currentRuleStatus = this.ruleStatus.find(r=>r.key === status);
+ if(status.trim() === '') {
+ this.changedPolicies = this.policies;
+ } else {
+ this.changedPolicies = this.policies.filter(policy=>policy.enabled === +this.currentRuleStatus.key);
+ }
+ }
+
+ doFilterJobStatus(status: string) {
+ console.log('Do filter jobs with status:' + status);
+ this.currentJobStatus = this.jobStatus.find(r=>r.key === status);
+ if(status.trim() === '') {
+ this.changedJobs = this.jobs;
+ } else {
+ this.changedJobs = this.jobs.filter(job=>job.status === status);
+ }
+ }
+
+ doSearchJobs(repoName: string) {
+ this.search.repoName = repoName;
+ this.fetchPolicyJobs(this.search.policyId);
+ }
+
+ reloadPolicies(isReady: boolean) {
+ if(isReady) {
+ this.retrievePolicies();
+ }
+ }
+
+ refreshPolicies() {
+ this.retrievePolicies();
+ }
+
+ refreshJobs() {
+ this.fetchPolicyJobs(this.search.policyId);
+ }
+
+ toggleSearchJobOptionalName(option: number) {
+ (option === 1) ? this.currentJobSearchOption = 0 : this.currentJobSearchOption = 1;
+ }
+
+ doJobSearchByTimeRange(strDate: string, target: string) {
+ if(!strDate || strDate.trim() === '') {
+ strDate = 0 + '';
+ }
+ let oneDayOffset = 3600 * 24;
+ switch(target) {
+ case 'begin':
+ this.search.startTime = (new Date(strDate).getTime() / 1000) + '';
+ break;
+ case 'end':
+ this.search.endTime = (new Date(strDate).getTime() / 1000 + oneDayOffset) + '';
+ break;
+ }
+ console.log('Search jobs filtered by time range, begin: ' + this.search.startTime + ', end:' + this.search.endTime);
+ this.fetchPolicyJobs(this.search.policyId);
+ }
+
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/replication.module.ts b/src/ui_ng/src/app/replication/replication.module.ts
index 31144d2af..962c74c93 100644
--- a/src/ui_ng/src/app/replication/replication.module.ts
+++ b/src/ui_ng/src/app/replication/replication.module.ts
@@ -1,23 +1,28 @@
import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { ReplicationManagementComponent } from './replication-management/replication-management.component';
import { ReplicationComponent } from './replication.component';
-import { CreateEditPolicyComponent } from './create-edit-policy/create-edit-policy.component';
-import { ListPolicyComponent } from './list-policy/list-policy.component';
import { ListJobComponent } from './list-job/list-job.component';
-
-import { CustomHighlightDirective } from './list-policy/custom-highlight.directive';
+import { TotalReplicationComponent } from './total-replication/total-replication.component';
+import { DestinationComponent } from './destination/destination.component';
+import { CreateEditDestinationComponent } from './create-edit-destination/create-edit-destination.component';
import { SharedModule } from '../shared/shared.module';
import { ReplicationService } from './replication.service';
@NgModule({
- imports: [ SharedModule ],
+ imports: [
+ SharedModule,
+ RouterModule
+ ],
declarations: [
ReplicationComponent,
- CreateEditPolicyComponent,
- ListPolicyComponent,
+ ReplicationManagementComponent,
ListJobComponent,
- CustomHighlightDirective
+ TotalReplicationComponent,
+ DestinationComponent,
+ CreateEditDestinationComponent
],
exports: [ ReplicationComponent ],
providers: [ ReplicationService ]
diff --git a/src/ui_ng/src/app/replication/replication.service.ts b/src/ui_ng/src/app/replication/replication.service.ts
index 39c67f1d0..5cefc34c8 100644
--- a/src/ui_ng/src/app/replication/replication.service.ts
+++ b/src/ui_ng/src/app/replication/replication.service.ts
@@ -1,17 +1,17 @@
import { Injectable } from '@angular/core';
-import { Http, URLSearchParams } from '@angular/http';
+import { Http, URLSearchParams, Response } from '@angular/http';
import { BaseService } from '../service/base.service';
import { Policy } from './policy';
import { Job } from './job';
+import { Target } from './target';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
-
-export const urlPrefix = '';
+import 'rxjs/add/operator/mergeMap';
@Injectable()
export class ReplicationService extends BaseService {
@@ -19,20 +19,154 @@ export class ReplicationService extends BaseService {
super();
}
- listPolicies(projectId: number, policyName: string): Observable {
+ listPolicies(policyName: string, projectId?: any): Observable {
+ if(!projectId) {
+ projectId = '';
+ }
console.log('Get policies with project ID:' + projectId + ', policy name:' + policyName);
return this.http
- .get(urlPrefix + `/api/policies/replication?project_id=${projectId}`)
+ .get(`/api/policies/replication?project_id=${projectId}&name=${policyName}`)
.map(response=>response.json() as Policy[])
.catch(error=>Observable.throw(error));
}
- // /api/jobs/replication/?page=1&page_size=20&end_time=&policy_id=1&start_time=&status=
- listJobs(policyId: number, status: string = ''): Observable {
+ getPolicy(policyId: number): Observable {
+ console.log('Get policy with ID:' + policyId);
+ return this.http
+ .get(`/api/policies/replication/${policyId}`)
+ .map(response=>response.json() as Policy)
+ .catch(error=>Observable.throw(error));
+ }
+
+ createPolicy(policy: Policy): Observable {
+ console.log('Create policy with project ID:' + policy.project_id + ', policy:' + JSON.stringify(policy));
+ return this.http
+ .post(`/api/policies/replication`, JSON.stringify(policy))
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+
+ updatePolicy(policy: Policy): Observable {
+ if (policy && policy.id) {
+ return this.http
+ .put(`/api/policies/replication/${policy.id}`, JSON.stringify(policy))
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+ return Observable.throw(new Error("Policy is nil or has no ID set."));
+ }
+
+ createOrUpdatePolicyWithNewTarget(policy: Policy, target: Target): Observable {
+ return this.http
+ .post(`/api/targets`, JSON.stringify(target))
+ .map(response=>{
+ return response.status;
+ })
+ .flatMap((status)=>{
+ if(status === 201) {
+ return this.http
+ .get(`/api/targets?name=${target.name}`)
+ .map(res=>res)
+ .catch(error=>Observable.throw(error));
+ }
+ })
+ .flatMap((res: Response) => {
+ if(res.status === 200) {
+ let lastAddedTarget= res.json()[0];
+ if(lastAddedTarget && lastAddedTarget.id) {
+ policy.target_id = lastAddedTarget.id;
+ if(policy.id) {
+ return this.http
+ .put(`/api/policies/replication/${policy.id}`, JSON.stringify(policy))
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ } else {
+ return this.http
+ .post(`/api/policies/replication`, JSON.stringify(policy))
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+ }
+ }
+ })
+ .catch(error=>Observable.throw(error));
+ }
+
+ enablePolicy(policyId: number, enabled: number): Observable {
+ console.log('Enable or disable policy ID:' + policyId + ' with activation status:' + enabled);
+ return this.http
+ .put(`/api/policies/replication/${policyId}/enablement`, {enabled: enabled})
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+
+ deletePolicy(policyId: number): Observable {
+ console.log('Delete policy ID:' + policyId);
+ return this.http
+ .delete(`/api/policies/replication/${policyId}`)
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+
+ // /api/jobs/replication/?page=1&page_size=20&end_time=&policy_id=1&start_time=&status=&repository=
+ listJobs(policyId: number, status: string = '', repoName: string = '', startTime: string = '', endTime: string = ''): Observable {
console.log('Get jobs under policy ID:' + policyId);
return this.http
- .get(urlPrefix + `/api/jobs/replication?policy_id=${policyId}&status=${status}`)
+ .get(`/api/jobs/replication?policy_id=${policyId}&status=${status}&repository=${repoName}&start_time=${startTime}&end_time=${endTime}`)
.map(response=>response.json() as Job[])
.catch(error=>Observable.throw(error));
}
+
+ listTargets(targetName: string): Observable {
+ console.log('Get targets.');
+ return this.http
+ .get(`/api/targets?name=${targetName}`)
+ .map(response=>response.json() as Target[])
+ .catch(error=>Observable.throw(error));
+ }
+
+ getTarget(targetId: number): Observable {
+ console.log('Get target by ID:' + targetId);
+ return this.http
+ .get(`/api/targets/${targetId}`)
+ .map(response=>response.json() as Target)
+ .catch(error=>Observable.throw(error));
+ }
+
+ createTarget(target: Target): Observable {
+ console.log('Create target:' + JSON.stringify(target));
+ return this.http
+ .post(`/api/targets`, JSON.stringify(target))
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+
+ pingTarget(target: Target): Observable {
+ console.log('Ping target.');
+ let body = new URLSearchParams();
+ body.set('endpoint', target.endpoint);
+ body.set('username', target.username);
+ body.set('password', target.password);
+ return this.http
+ .post(`/api/targets/ping`, body)
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+
+ updateTarget(target: Target): Observable {
+ console.log('Update target with target ID' + target.id);
+ return this.http
+ .put(`/api/targets/${target.id}`, JSON.stringify(target))
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+
+ deleteTarget(targetId: number): Observable {
+ console.log('Deleting target with ID:' + targetId);
+ return this.http
+ .delete(`/api/targets/${targetId}`)
+ .map(response=>response.status)
+ .catch(error=>Observable.throw(error));
+ }
+
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/total-replication/total-replication.component.html b/src/ui_ng/src/app/replication/total-replication/total-replication.component.html
new file mode 100644
index 000000000..8877c8a68
--- /dev/null
+++ b/src/ui_ng/src/app/replication/total-replication/total-replication.component.html
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/total-replication/total-replication.component.ts b/src/ui_ng/src/app/replication/total-replication/total-replication.component.ts
new file mode 100644
index 000000000..7e5858aa8
--- /dev/null
+++ b/src/ui_ng/src/app/replication/total-replication/total-replication.component.ts
@@ -0,0 +1,71 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { ReplicationService } from '../../replication/replication.service';
+
+import { CreateEditPolicyComponent } from '../../shared/create-edit-policy/create-edit-policy.component';
+
+import { MessageService } from '../../global-message/message.service';
+import { AlertType } from '../../shared/shared.const';
+
+import { Policy } from '../../replication/policy';
+
+@Component({
+ selector: 'total-replication',
+ templateUrl: 'total-replication.component.html',
+ providers: [ ReplicationService ]
+})
+export class TotalReplicationComponent implements OnInit {
+
+ changedPolicies: Policy[];
+ policies: Policy[];
+ policyName: string = '';
+ projectId: number;
+
+ @ViewChild(CreateEditPolicyComponent)
+ createEditPolicyComponent: CreateEditPolicyComponent;
+
+ constructor(
+ private replicationService: ReplicationService,
+ private messageService: MessageService) {}
+
+ ngOnInit() {
+ this.retrievePolicies();
+ }
+
+ retrievePolicies(): void {
+ this.replicationService
+ .listPolicies(this.policyName)
+ .subscribe(
+ response=>{
+ this.changedPolicies = response;
+ this.policies = this.changedPolicies;
+ },
+ error=>this.messageService.announceMessage(error.status,'Failed to get policies.', AlertType.DANGER)
+ );
+ }
+
+ doSearchPolicies(policyName: string) {
+ this.policyName = policyName;
+ this.retrievePolicies();
+ }
+
+ openEditPolicy(policyId: number) {
+ console.log('Open modal to edit policy ID:' + policyId);
+ this.createEditPolicyComponent.openCreateEditPolicy(policyId);
+ }
+
+ selectPolicy(policy: Policy) {
+ if(policy) {
+ this.projectId = policy.project_id;
+ }
+ }
+
+ refreshPolicies() {
+ this.retrievePolicies();
+ }
+
+ reloadPolicies(isReady: boolean) {
+ if(isReady) {
+ this.retrievePolicies();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/repository/repository.component.html b/src/ui_ng/src/app/repository/repository.component.html
index bffb836bf..feb83bcf9 100644
--- a/src/ui_ng/src/app/repository/repository.component.html
+++ b/src/ui_ng/src/app/repository/repository.component.html
@@ -1,7 +1,7 @@
-
-
diff --git a/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.css b/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.css
new file mode 100644
index 000000000..4a2236d0e
--- /dev/null
+++ b/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.css
@@ -0,0 +1,37 @@
+.margin-left-override {
+ margin-left: 24px !important;
+}
+
+.about-text-link {
+ font-family: "Proxima Nova Light";
+ font-size: 14px;
+ color: #007CBB;
+ line-height: 24px;
+}
+
+.about-copyright-text {
+ font-family: "Proxima Nova Light";
+ font-size: 13px;
+ color: #565656;
+ line-height: 16px;
+}
+
+.about-product-title {
+ font-family: "Metropolis Light";
+ font-size: 28px;
+ color: #000000;
+ line-height: 36px;
+}
+
+.about-version {
+ font-family: "Metropolis";
+ font-size: 14px;
+ color: #565656;
+ font-weight: 500;
+}
+
+.about-build {
+ font-family: "Metropolis";
+ font-size: 14px;
+ color: #565656;
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.html b/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.html
new file mode 100644
index 000000000..2a676ff92
--- /dev/null
+++ b/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.html
@@ -0,0 +1,25 @@
+
+ vmware
+
+
Harbor
+
+
+ {{'ABOUT.VERSION' | translate}} {{version}}
+ |
+ {{'ABOUT.BUILD' | translate}} {{build}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.ts b/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.ts
new file mode 100644
index 000000000..adea99883
--- /dev/null
+++ b/src/ui_ng/src/app/shared/about-dialog/about-dialog.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'about-dialog',
+ templateUrl: "about-dialog.component.html",
+ styleUrls: ["about-dialog.component.css"]
+})
+export class AboutDialogComponent {
+ private opened: boolean = false;
+ private version: string ="0.4.1";
+ private build: string ="4276418";
+
+ public open(): void {
+ this.opened = true;
+ }
+
+ public close(): void {
+ this.opened = false;
+ }
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.html b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.html
new file mode 100644
index 000000000..08dad367f
--- /dev/null
+++ b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.html
@@ -0,0 +1,81 @@
+
+ New Replication Rule
+
+
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts
new file mode 100644
index 000000000..eecf6411b
--- /dev/null
+++ b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.component.ts
@@ -0,0 +1,228 @@
+import { Component, Input, Output, EventEmitter, OnInit, HostBinding } from '@angular/core';
+
+import { CreateEditPolicy } from './create-edit-policy';
+
+import { ReplicationService } from '../../replication/replication.service';
+import { MessageService } from '../../global-message/message.service';
+import { AlertType, ActionType } from '../../shared/shared.const';
+
+import { Policy } from '../../replication/policy';
+import { Target } from '../../replication/target';
+
+@Component({
+ selector: 'create-edit-policy',
+ templateUrl: 'create-edit-policy.component.html'
+})
+export class CreateEditPolicyComponent implements OnInit {
+
+ createEditPolicyOpened: boolean;
+ createEditPolicy: CreateEditPolicy = new CreateEditPolicy();
+
+ actionType: ActionType;
+
+ errorMessageOpened: boolean;
+ errorMessage: string;
+
+ isCreateDestination: boolean;
+ @Input() projectId: number;
+
+ @Output() reload = new EventEmitter();
+
+ targets: Target[];
+
+ pingTestMessage: string;
+ testOngoing: boolean;
+ pingStatus: boolean;
+
+ constructor(private replicationService: ReplicationService,
+ private messageService: MessageService) {}
+
+ prepareTargets(targetId?: number) {
+ this.replicationService
+ .listTargets('')
+ .subscribe(
+ targets=>{
+ this.targets = targets;
+ if(this.targets && this.targets.length > 0) {
+ let initialTarget: Target;
+ (targetId) ? initialTarget = this.targets.find(t=>t.id==targetId) : initialTarget = this.targets[0];
+ this.createEditPolicy.targetId = initialTarget.id;
+ this.createEditPolicy.targetName = initialTarget.name;
+ this.createEditPolicy.endpointUrl = initialTarget.endpoint;
+ this.createEditPolicy.username = initialTarget.username;
+ this.createEditPolicy.password = initialTarget.password;
+ }
+ },
+ error=>this.messageService.announceMessage(error.status, 'Error occurred while get targets.', AlertType.DANGER)
+ );
+ }
+
+ ngOnInit(): void {}
+
+ openCreateEditPolicy(policyId?: number): void {
+ this.createEditPolicyOpened = true;
+ this.createEditPolicy = new CreateEditPolicy();
+ this.isCreateDestination = false;
+ this.errorMessageOpened = false;
+ this.errorMessage = '';
+
+ this.pingTestMessage = '';
+ this.pingStatus = true;
+ this.testOngoing = false;
+
+ if(policyId) {
+ this.actionType = ActionType.EDIT;
+ this.replicationService
+ .getPolicy(policyId)
+ .subscribe(
+ policy=>{
+ this.createEditPolicy.policyId = policyId;
+ this.createEditPolicy.name = policy.name;
+ this.createEditPolicy.description = policy.description;
+ this.createEditPolicy.enable = policy.enabled === 1? true : false;
+ this.prepareTargets(policy.target_id);
+ }
+ )
+ } else {
+ this.actionType = ActionType.ADD_NEW;
+ this.prepareTargets();
+ }
+ }
+
+ newDestination(checkedAddNew: boolean): void {
+ console.log('CheckedAddNew:' + checkedAddNew);
+ this.isCreateDestination = checkedAddNew;
+ this.createEditPolicy.targetName = '';
+ this.createEditPolicy.endpointUrl = '';
+ this.createEditPolicy.username = '';
+ this.createEditPolicy.password = '';
+ }
+
+ selectTarget(): void {
+ let result = this.targets.find(target=>target.id == this.createEditPolicy.targetId);
+ if(result) {
+ this.createEditPolicy.targetId = result.id;
+ this.createEditPolicy.endpointUrl = result.endpoint;
+ this.createEditPolicy.username = result.username;
+ this.createEditPolicy.password = result.password;
+ }
+ }
+
+ onErrorMessageClose(): void {
+ this.errorMessageOpened = false;
+ this.errorMessage = '';
+ }
+
+ getPolicyByForm(): Policy {
+ let policy = new Policy();
+ policy.project_id = this.projectId;
+ policy.id = this.createEditPolicy.policyId;
+ policy.name = this.createEditPolicy.name;
+ policy.description = this.createEditPolicy.description;
+ policy.enabled = this.createEditPolicy.enable ? 1 : 0;
+ policy.target_id = this.createEditPolicy.targetId;
+ return policy;
+ }
+
+ getTargetByForm(): Target {
+ let target = new Target();
+ target.id = this.createEditPolicy.targetId;
+ target.name = this.createEditPolicy.targetName;
+ target.endpoint = this.createEditPolicy.endpointUrl;
+ target.username = this.createEditPolicy.username;
+ target.password = this.createEditPolicy.password;
+ return target;
+ }
+
+ createPolicy(): void {
+ console.log('Create policy with existing target in component.');
+ this.replicationService
+ .createPolicy(this.getPolicyByForm())
+ .subscribe(
+ response=>{
+ console.log('Successful created policy: ' + response);
+ this.createEditPolicyOpened = false;
+ this.reload.emit(true);
+ },
+ error=>{
+ this.errorMessageOpened = true;
+ this.errorMessage = error['_body'];
+ console.log('Failed to create policy:' + error.status + ', error message:' + JSON.stringify(error['_body']));
+ });
+ }
+
+ createOrUpdatePolicyAndCreateTarget(): void {
+ console.log('Creating policy with new created target.');
+ this.replicationService
+ .createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm())
+ .subscribe(
+ response=>{
+ console.log('Successful created policy and target:' + response);
+ this.createEditPolicyOpened = false;
+ this.reload.emit(true);
+ },
+ error=>{
+ this.errorMessageOpened = true;
+ this.errorMessage = error['_body'];
+ console.log('Failed to create policy and target:' + error.status + ', error message:' + JSON.stringify(error['_body']));
+ }
+ );
+ }
+
+ updatePolicy(): void {
+ console.log('Creating policy with existing target.');
+ this.replicationService
+ .updatePolicy(this.getPolicyByForm())
+ .subscribe(
+ response=>{
+ console.log('Successful created policy and target:' + response);
+ this.createEditPolicyOpened = false;
+ this.reload.emit(true);
+ },
+ error=>{
+ this.errorMessageOpened = true;
+ this.errorMessage = error['_body'];
+ console.log('Failed to create policy and target:' + error.status + ', error message:' + JSON.stringify(error['_body']));
+ }
+ );
+ }
+
+ onSubmit() {
+ if(this.isCreateDestination) {
+ this.createOrUpdatePolicyAndCreateTarget();
+ } else {
+ if(this.actionType === ActionType.ADD_NEW) {
+ this.createPolicy();
+ } else if(this.actionType === ActionType.EDIT){
+ this.updatePolicy();
+ }
+ }
+
+ this.errorMessageOpened = false;
+ this.errorMessage = '';
+ }
+
+ testConnection() {
+ this.pingStatus = true;
+ this.pingTestMessage = 'Testing connection...';
+ this.testOngoing = !this.testOngoing;
+ let pingTarget = new Target();
+ pingTarget.endpoint = this.createEditPolicy.endpointUrl;
+ pingTarget.username = this.createEditPolicy.username;
+ pingTarget.password = this.createEditPolicy.password;
+ this.replicationService
+ .pingTarget(pingTarget)
+ .subscribe(
+ response=>{
+ this.testOngoing = !this.testOngoing;
+ this.pingTestMessage = 'Connection tested successfully.';
+ this.pingStatus = true;
+ },
+ error=>{
+ this.testOngoing = !this.testOngoing;
+ this.pingTestMessage = 'Failed to ping target.';
+ this.pingStatus = false;
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.ts b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.ts
new file mode 100644
index 000000000..7725e7e4f
--- /dev/null
+++ b/src/ui_ng/src/app/shared/create-edit-policy/create-edit-policy.ts
@@ -0,0 +1,11 @@
+export class CreateEditPolicy {
+ policyId: number;
+ name: string;
+ description: string;
+ enable: boolean;
+ targetId: number;
+ targetName: string;
+ endpointUrl: string;
+ username: string;
+ password: string;
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/deletion-dialog/deletion-dialog.component.ts b/src/ui_ng/src/app/shared/deletion-dialog/deletion-dialog.component.ts
index 6ea70e8f9..052479c55 100644
--- a/src/ui_ng/src/app/shared/deletion-dialog/deletion-dialog.component.ts
+++ b/src/ui_ng/src/app/shared/deletion-dialog/deletion-dialog.component.ts
@@ -1,6 +1,9 @@
-import { Component } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
+import { Subscription } from 'rxjs/Subscription';
+import { TranslateService } from '@ngx-translate/core';
import { DeletionDialogService } from './deletion-dialog.service';
+import { DeletionMessage } from './deletion-message';
@Component({
selector: 'deletion-dialog',
@@ -8,20 +11,32 @@ import { DeletionDialogService } from './deletion-dialog.service';
styleUrls: ['deletion-dialog.component.css']
})
-export class DeletionDialogComponent{
+export class DeletionDialogComponent implements OnDestroy{
opened: boolean = false;
dialogTitle: string = "";
dialogContent: string = "";
- data: any;
+ message: DeletionMessage;
+ private annouceSubscription: Subscription;
- constructor(private delService: DeletionDialogService){
- delService.deletionAnnouced$.subscribe(msg => {
- this.dialogTitle = msg.title;
- this.dialogContent = msg.message;
- this.data = msg.data;
- //Open dialog
- this.open();
- });
+ constructor(
+ private delService: DeletionDialogService,
+ private translate: TranslateService) {
+ this.annouceSubscription = delService.deletionAnnouced$.subscribe(msg => {
+ this.dialogTitle = msg.title;
+ this.dialogContent = msg.message;
+ this.message = msg;
+
+ this.translate.get(this.dialogTitle).subscribe((res: string) => this.dialogTitle = res);
+ this.translate.get(this.dialogContent, { 'param': msg.param }).subscribe((res: string) => this.dialogContent = res);
+ //Open dialog
+ this.open();
+ });
+ }
+
+ ngOnDestroy(): void {
+ if(this.annouceSubscription){
+ this.annouceSubscription.unsubscribe();
+ }
}
open(): void {
@@ -33,7 +48,7 @@ export class DeletionDialogComponent{
}
confirm(): void {
- this.delService.confirmDeletion(this.data);
+ this.delService.confirmDeletion(this.message);
this.close();
}
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/deletion-dialog/deletion-dialog.service.ts b/src/ui_ng/src/app/shared/deletion-dialog/deletion-dialog.service.ts
index 8563366c1..ae926cb9b 100644
--- a/src/ui_ng/src/app/shared/deletion-dialog/deletion-dialog.service.ts
+++ b/src/ui_ng/src/app/shared/deletion-dialog/deletion-dialog.service.ts
@@ -6,13 +6,13 @@ import { DeletionMessage } from './deletion-message';
@Injectable()
export class DeletionDialogService {
private deletionAnnoucedSource = new Subject
();
- private deletionConfirmSource = new Subject();
+ private deletionConfirmSource = new Subject();
deletionAnnouced$ = this.deletionAnnoucedSource.asObservable();
deletionConfirm$ = this.deletionConfirmSource.asObservable();
- confirmDeletion(obj: any): void {
- this.deletionConfirmSource.next(obj);
+ confirmDeletion(message: any): void {
+ this.deletionConfirmSource.next(message);
}
openComfirmDialog(message: DeletionMessage): void {
diff --git a/src/ui_ng/src/app/shared/deletion-dialog/deletion-message.ts b/src/ui_ng/src/app/shared/deletion-dialog/deletion-message.ts
index c59cd073b..5545394b3 100644
--- a/src/ui_ng/src/app/shared/deletion-dialog/deletion-message.ts
+++ b/src/ui_ng/src/app/shared/deletion-dialog/deletion-message.ts
@@ -1,10 +1,16 @@
+import { DeletionTargets } from '../../shared/shared.const';
+
export class DeletionMessage {
- public constructor(title: string, message: string, data: any){
+ public constructor(title: string, message: string, param: string, data: any, targetId: DeletionTargets) {
this.title = title;
this.message = message;
this.data = data;
+ this.targetId = targetId;
+ this.param = param;
}
title: string;
message: string;
data: any;
+ targetId: DeletionTargets = DeletionTargets.EMPTY;
+ param: string;
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/inline-alert/inline-alert.component.html b/src/ui_ng/src/app/shared/inline-alert/inline-alert.component.html
new file mode 100644
index 000000000..926d70632
--- /dev/null
+++ b/src/ui_ng/src/app/shared/inline-alert/inline-alert.component.html
@@ -0,0 +1,10 @@
+
+
+
+ {{errorMessage}}
+
+
+ {{'BUTTON.CONFIRM' | translate}}
+
+
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/inline-alert/inline-alert.component.ts b/src/ui_ng/src/app/shared/inline-alert/inline-alert.component.ts
new file mode 100644
index 000000000..ca9e8a7e3
--- /dev/null
+++ b/src/ui_ng/src/app/shared/inline-alert/inline-alert.component.ts
@@ -0,0 +1,71 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+
+import { errorHandler } from '../shared.utils';
+
+@Component({
+ selector: 'inline-alert',
+ templateUrl: "inline-alert.component.html"
+})
+export class InlineAlertComponent {
+ private inlineAlertType: string = 'alert-danger';
+ private inlineAlertClosable: boolean = true;
+ private alertClose: boolean = true;
+ private displayedText: string = "";
+ private showCancelAction: boolean = false;
+ private useAppLevelStyle: boolean = false;
+
+ @Output() confirmEvt = new EventEmitter();
+
+ constructor(private translate: TranslateService){}
+
+ public get errorMessage(): string {
+ return this.displayedText;
+ }
+
+ //Show error message inline
+ public showInlineError(error: any): void {
+ this.displayedText = errorHandler(error);
+
+ this.inlineAlertType = 'alert-danger';
+ this.showCancelAction = false;
+ this.inlineAlertClosable = true;
+ this.alertClose = false;
+ this.useAppLevelStyle = false;
+ }
+
+ //Show confirmation info with action button
+ public showInlineConfirmation(warning: any): void {
+ this.displayedText = "";
+ if(warning && warning.message){
+ this.translate.get(warning.message).subscribe((res: string) => this.displayedText = res);
+ }
+ this.inlineAlertType = 'alert-warning';
+ this.showCancelAction = true;
+ this.inlineAlertClosable = true;
+ this.alertClose = false;
+ this.useAppLevelStyle = true;
+ }
+
+ //Show inline sccess info
+ public showInlineSuccess(info: any): void {
+ this.displayedText = "";
+ if(info && info.message){
+ this.translate.get(info.message).subscribe((res: string) => this.displayedText = res);
+ }
+ this.inlineAlertType = 'alert-success';
+ this.showCancelAction = false;
+ this.inlineAlertClosable = true;
+ this.alertClose = false;
+ this.useAppLevelStyle = false;
+ }
+
+ //Close alert
+ public close(): void {
+ this.alertClose = true;
+ }
+
+ private confirmCancel(): void {
+ this.confirmEvt.emit(true);
+ }
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/list-policy/list-policy.component.html b/src/ui_ng/src/app/shared/list-policy/list-policy.component.html
new file mode 100644
index 000000000..4636de768
--- /dev/null
+++ b/src/ui_ng/src/app/shared/list-policy/list-policy.component.html
@@ -0,0 +1,24 @@
+
+ Name
+ Project
+ Description
+ Destination
+ Last Start Time
+ Activation
+
+ {{p.name}}
+ {{p.project_name}}
+ {{p.description}}
+ {{p.target_name}}
+ {{p.start_time}}
+
+ {{p.enabled === 1 ? 'Enabled' : 'Disabled'}}
+
+ Edit Policy
+ {{ p.enabled === 0 ? 'Enable' : 'Disabled' }}
+ Delete
+
+
+
+ {{ (policies ? policies.length : 0) }} item(s)
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts b/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts
new file mode 100644
index 000000000..928e8e279
--- /dev/null
+++ b/src/ui_ng/src/app/shared/list-policy/list-policy.component.ts
@@ -0,0 +1,88 @@
+import { Component, Input, Output, EventEmitter, HostBinding, OnInit, ViewChild, OnDestroy } from '@angular/core';
+
+import { ReplicationService } from '../../replication/replication.service';
+import { Policy } from '../../replication/policy';
+
+import { DeletionDialogService } from '../../shared/deletion-dialog/deletion-dialog.service';
+import { DeletionMessage } from '../../shared/deletion-dialog/deletion-message';
+
+import { DeletionTargets } from '../../shared/shared.const';
+
+import { MessageService } from '../../global-message/message.service';
+import { AlertType } from '../../shared/shared.const';
+
+import { Subscription } from 'rxjs/Subscription';
+
+@Component({
+ selector: 'list-policy',
+ templateUrl: 'list-policy.component.html',
+})
+export class ListPolicyComponent implements OnInit, OnDestroy {
+
+ @Input() policies: Policy[];
+ @Input() projectless: boolean;
+
+ @Output() reload = new EventEmitter();
+ @Output() selectOne = new EventEmitter();
+ @Output() editOne = new EventEmitter();
+
+ selectedId: number;
+ subscription: Subscription;
+
+ constructor(
+ private replicationService: ReplicationService,
+ private deletionDialogService: DeletionDialogService,
+ private messageService: MessageService) {
+
+ this.subscription = this.subscription = this.deletionDialogService
+ .deletionConfirm$
+ .subscribe(
+ message=>{
+ if(message && message.targetId === DeletionTargets.POLICY) {
+ this.replicationService
+ .deletePolicy(message.data)
+ .subscribe(
+ response=>{
+ console.log('Successful delete policy with ID:' + message.data);
+ this.reload.emit(true);
+ },
+ error=>this.messageService.announceMessage(error.status, 'Failed to delete policy with ID:' + message.data, AlertType.DANGER)
+ );
+ }
+ });
+
+ }
+
+ ngOnInit() {
+
+ }
+
+ ngOnDestroy() {
+ if(this.subscription) {
+ this.subscription.unsubscribe();
+ }
+ }
+
+ selectPolicy(policy: Policy): void {
+ this.selectedId = policy.id;
+ console.log('Select policy ID:' + policy.id);
+ this.selectOne.emit(policy);
+ }
+
+ editPolicy(policy: Policy) {
+ console.log('Open modal to edit policy.');
+ this.editOne.emit(policy.id);
+ }
+
+ enablePolicy(policy: Policy): void {
+ console.log('Enable policy ID:' + policy.id + ' with activation status ' + policy.enabled);
+ policy.enabled = policy.enabled === 0 ? 1 : 0;
+ this.replicationService.enablePolicy(policy.id, policy.enabled);
+ }
+
+ deletePolicy(policy: Policy) {
+ let deletionMessage: DeletionMessage = new DeletionMessage('REPLICATION.DELETION_TITLE', 'REPLICATION.DELETION_SUMMARY', policy.name, policy.id, DeletionTargets.POLICY);
+ this.deletionDialogService.openComfirmDialog(deletionMessage);
+ }
+
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.css b/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.css
new file mode 100644
index 000000000..f75fba1e0
--- /dev/null
+++ b/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.css
@@ -0,0 +1,5 @@
+.label-info {
+ margin: 0px !important;
+ padding: 0px !important;
+ margin-top: -5px !important;
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/user/new-user-form.component.html b/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.html
similarity index 63%
rename from src/ui_ng/src/app/user/new-user-form.component.html
rename to src/ui_ng/src/app/shared/new-user-form/new-user-form.component.html
index 9caa705b2..bc753157c 100644
--- a/src/ui_ng/src/app/user/new-user-form.component.html
+++ b/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.html
@@ -3,8 +3,8 @@
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/user/new-user-form.component.ts b/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.ts
similarity index 64%
rename from src/ui_ng/src/app/user/new-user-form.component.ts
rename to src/ui_ng/src/app/shared/new-user-form/new-user-form.component.ts
index 1e47d6495..774f3928d 100644
--- a/src/ui_ng/src/app/user/new-user-form.component.ts
+++ b/src/ui_ng/src/app/shared/new-user-form/new-user-form.component.ts
@@ -1,16 +1,19 @@
-import { Component, ViewChild, AfterViewChecked, Output, EventEmitter } from '@angular/core';
+import { Component, ViewChild, AfterViewChecked, Output, EventEmitter, Input } from '@angular/core';
import { NgForm } from '@angular/forms';
-import { User } from './user';
+import { User } from '../../user/user';
+import { isEmptyForm } from '../../shared/shared.utils';
@Component({
selector: 'new-user-form',
- templateUrl: 'new-user-form.component.html'
+ templateUrl: 'new-user-form.component.html',
+ styleUrls: ['new-user-form.component.css']
})
export class NewUserFormComponent implements AfterViewChecked {
newUser: User = new User();
confirmedPwd: string = "";
+ @Input() isSelfRegistration: boolean = false;
newUserFormRef: NgForm;
@ViewChild("newUserFrom") newUserForm: NgForm;
@@ -20,12 +23,12 @@ export class NewUserFormComponent implements AfterViewChecked {
public get isValid(): boolean {
let pwdEqualStatus = true;
- if(this.newUserForm.controls["confirmPassword"] &&
- this.newUserForm.controls["newPassword"]){
+ if (this.newUserForm.controls["confirmPassword"] &&
+ this.newUserForm.controls["newPassword"]) {
pwdEqualStatus = this.newUserForm.controls["confirmPassword"].value === this.newUserForm.controls["newPassword"].value;
}
- return this.newUserForm &&
- this.newUserForm.valid && pwdEqualStatus;
+ return this.newUserForm &&
+ this.newUserForm.valid && pwdEqualStatus;
}
ngAfterViewChecked(): void {
@@ -44,9 +47,15 @@ export class NewUserFormComponent implements AfterViewChecked {
return this.newUser;
}
+ //Reset form
reset(): void {
- if(this.newUserForm){
+ if (this.newUserForm) {
this.newUserForm.reset();
}
}
+
+ //To check if form is empty
+ isEmpty(): boolean {
+ return isEmptyForm(this.newUserForm);
+ }
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/not-found/not-found.component.css b/src/ui_ng/src/app/shared/not-found/not-found.component.css
new file mode 100644
index 000000000..9bbdb1397
--- /dev/null
+++ b/src/ui_ng/src/app/shared/not-found/not-found.component.css
@@ -0,0 +1,33 @@
+.wrapper-back {
+ position: absolute;
+ top: 50%;
+ height: 240px;
+ margin-top: -120px;
+ text-align: center;
+ left: 50%;
+ margin-left: -300px;
+}
+
+.status-code {
+ font-weight: bolder;
+ font-size: 4em;
+ color: #A32100;
+ vertical-align: middle;
+}
+
+.status-text {
+ font-weight: bold;
+ font-size: 3em;
+ margin-left: 10px;
+ vertical-align: middle;
+}
+
+.status-subtitle {
+ font-size: 18px;
+}
+
+.second-number {
+ font-weight: bold;
+ font-size: 2em;
+ color: #EB8D00;
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/not-found/not-found.component.html b/src/ui_ng/src/app/shared/not-found/not-found.component.html
new file mode 100644
index 000000000..29e676128
--- /dev/null
+++ b/src/ui_ng/src/app/shared/not-found/not-found.component.html
@@ -0,0 +1,10 @@
+
+
+
+ 404
+ {{'PAGE_NOT_FOUND.MAIN_TITLE' | translate}}
+
+
+ {{'PAGE_NOT_FOUND.SUB_TITLE' | translate}} {{leftSeconds}} {{'PAGE_NOT_FOUND.UNIT' | translate}}
+
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/not-found/not-found.component.ts b/src/ui_ng/src/app/shared/not-found/not-found.component.ts
new file mode 100644
index 000000000..6f0ebc930
--- /dev/null
+++ b/src/ui_ng/src/app/shared/not-found/not-found.component.ts
@@ -0,0 +1,35 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Router } from '@angular/router';
+
+const defaultInterval = 1000;
+const defaultLeftTime = 5;
+
+@Component({
+ selector: 'page-not-found',
+ templateUrl: "not-found.component.html",
+ styleUrls: ['not-found.component.css']
+})
+export class PageNotFoundComponent implements OnInit, OnDestroy{
+ private leftSeconds: number = defaultLeftTime;
+ private timeInterval: any = null;
+
+ constructor(private router: Router){}
+
+ ngOnInit(): void {
+ if(!this.timeInterval){
+ this.timeInterval = setInterval(interval => {
+ this.leftSeconds--;
+ if(this.leftSeconds <= 0){
+ this.router.navigate(['harbor']);
+ clearInterval(this.timeInterval);
+ }
+ }, defaultInterval);
+ }
+ }
+
+ ngOnDestroy(): void {
+ if(this.timeInterval){
+ clearInterval(this.timeInterval);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/port.directive.ts b/src/ui_ng/src/app/shared/port.directive.ts
new file mode 100644
index 000000000..1ec66796a
--- /dev/null
+++ b/src/ui_ng/src/app/shared/port.directive.ts
@@ -0,0 +1,37 @@
+import { Directive } from '@angular/core';
+import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, Validators } from '@angular/forms';
+
+export const portNumbers = /[\d]+/;
+
+export function portValidator(): ValidatorFn {
+ return (control: AbstractControl): { [key: string]: any } => {
+ const value: string = control.value
+ if (!value) {
+ return { 'port': 65535 };
+ }
+
+ const regExp = new RegExp(portNumbers, 'i');
+ if(!regExp.test(value)){
+ return { 'port': 65535 };
+ }else{
+ const portV = parseInt(value);
+ if(portV <=0 || portV >65535){
+ return { 'port': 65535 };
+ }
+ }
+ return null;
+ }
+}
+
+@Directive({
+ selector: '[port]',
+ providers: [{ provide: NG_VALIDATORS, useExisting: PortValidatorDirective, multi: true }]
+})
+
+export class PortValidatorDirective implements Validator {
+ private valFn = portValidator();
+
+ validate(control: AbstractControl): { [key: string]: any } {
+ return this.valFn(control);
+ }
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/route/base-routing-resolver.service.ts b/src/ui_ng/src/app/shared/route/base-routing-resolver.service.ts
new file mode 100644
index 000000000..ff3ba02c7
--- /dev/null
+++ b/src/ui_ng/src/app/shared/route/base-routing-resolver.service.ts
@@ -0,0 +1,37 @@
+import { Injectable } from '@angular/core';
+import {
+ Router,
+ Resolve,
+ ActivatedRouteSnapshot,
+ RouterStateSnapshot,
+ NavigationExtras
+} from '@angular/router';
+
+import { SessionService } from '../../shared/session.service';
+import { SessionUser } from '../../shared/session-user';
+import { harborRootRoute } from '../shared.const';
+
+@Injectable()
+export class BaseRoutingResolver implements Resolve
{
+
+ constructor(private session: SessionService, private router: Router) { }
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise {
+ //To refresh seesion
+ return this.session.retrieveUser()
+ .then(sessionUser => {
+ return sessionUser;
+ })
+ .catch(error => {
+ //Session retrieving failed then redirect to sign-in
+ //no matter what status code is.
+ //Please pay attention that route 'harborRootRoute' support anonymous user
+ if (state.url != harborRootRoute) {
+ let navigatorExtra: NavigationExtras = {
+ queryParams: {"redirect_url": state.url}
+ };
+ this.router.navigate(['sign-in'], navigatorExtra);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/route/system-admin-activate.service.ts b/src/ui_ng/src/app/shared/route/system-admin-activate.service.ts
new file mode 100644
index 000000000..e58e14196
--- /dev/null
+++ b/src/ui_ng/src/app/shared/route/system-admin-activate.service.ts
@@ -0,0 +1,29 @@
+import { Injectable } from '@angular/core';
+import {
+ CanActivate, Router,
+ ActivatedRouteSnapshot,
+ RouterStateSnapshot,
+ CanActivateChild
+} from '@angular/router';
+import { SessionService } from '../../shared/session.service';
+import { harborRootRoute } from '../../shared/shared.const';
+
+@Injectable()
+export class SystemAdminGuard implements CanActivate, CanActivateChild {
+ constructor(private authService: SessionService, private router: Router) { }
+
+ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
+ let sessionUser = this.authService.getCurrentUser();
+
+ let validation = sessionUser != null && sessionUser.has_admin_role > 0;
+ if (!validation) {
+ this.router.navigateByUrl(harborRootRoute);
+ }
+
+ return validation;
+ }
+
+ canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
+ return this.canActivate(route, state);
+ }
+}
diff --git a/src/ui_ng/src/app/shared/session.service.ts b/src/ui_ng/src/app/shared/session.service.ts
index 7dacc4533..f20b1e3c2 100644
--- a/src/ui_ng/src/app/shared/session.service.ts
+++ b/src/ui_ng/src/app/shared/session.service.ts
@@ -4,12 +4,17 @@ import 'rxjs/add/operator/toPromise';
import { SessionUser } from './session-user';
import { SignInCredential } from './sign-in-credential';
+import { enLang } from '../shared/shared.const'
-const urlPrefix = '';
-const signInUrl = urlPrefix + '/login';
-const currentUserEndpint = urlPrefix + "/api/users/current";
-const signOffEndpoint = urlPrefix + "/log_out";
-const accountEndpoint = urlPrefix + "/api/users/:id";
+const signInUrl = '/login';
+const currentUserEndpint = "/api/users/current";
+const signOffEndpoint = "/log_out";
+const accountEndpoint = "/api/users/:id";
+const langEndpoint = "/language";
+const langMap = {
+ "zh": "zh-CN",
+ "en": "en-US"
+};
/**
* Define related methods to handle account and session corresponding things
@@ -29,15 +34,15 @@ export class SessionService {
"Content-Type": 'application/x-www-form-urlencoded'
});
- constructor(private http: Http) {}
+ constructor(private http: Http) { }
//Handle the related exceptions
- private handleError(error: any): Promise{
+ private handleError(error: any): Promise {
return Promise.reject(error.message || error);
}
//Submit signin form to backend (NOT restful service)
- signIn(signInCredential: SignInCredential): Promise{
+ signIn(signInCredential: SignInCredential): Promise {
//Build the form package
const body = new URLSearchParams();
body.set('principal', signInCredential.principal);
@@ -45,9 +50,9 @@ export class SessionService {
//Trigger Http
return this.http.post(signInUrl, body.toString(), { headers: this.formHeaders })
- .toPromise()
- .then(()=>null)
- .catch(error => this.handleError(error));
+ .toPromise()
+ .then(() => null)
+ .catch(error => this.handleError(error));
}
/**
@@ -78,11 +83,11 @@ export class SessionService {
*/
signOff(): Promise {
return this.http.get(signOffEndpoint, { headers: this.headers }).toPromise()
- .then(() => {
- //Destroy current session cache
- this.currentUser = null;
- }) //Nothing returned
- .catch(error => this.handleError(error))
+ .then(() => {
+ //Destroy current session cache
+ this.currentUser = null;
+ }) //Nothing returned
+ .catch(error => this.handleError(error))
}
/**
@@ -94,17 +99,35 @@ export class SessionService {
*
* @memberOf SessionService
*/
- updateAccountSettings(account: SessionUser): Promise{
- if(!account){
+ updateAccountSettings(account: SessionUser): Promise {
+ if (!account) {
return Promise.reject("Invalid account settings");
}
- console.info(account);
- let putUrl = accountEndpoint.replace(":id", account.user_id+"");
+ let putUrl = accountEndpoint.replace(":id", account.user_id + "");
return this.http.put(putUrl, JSON.stringify(account), { headers: this.headers }).toPromise()
- .then(() => {
- //Retrieve current session user
- return this.retrieveUser();
- })
+ .then(() => {
+ //Retrieve current session user
+ return this.retrieveUser();
+ })
+ .catch(error => this.handleError(error))
+ }
+
+ /**
+ * Switch the backend language profile
+ */
+ switchLanguage(lang: string): Promise {
+ if (!lang) {
+ return Promise.reject("Invalid language");
+ }
+
+ let backendLang = langMap[lang];
+ if(!backendLang){
+ backendLang = langMap[enLang];
+ }
+
+ let getUrl = langEndpoint + "?lang=" + backendLang;
+ return this.http.get(getUrl).toPromise()
+ .then(() => null)
.catch(error => this.handleError(error))
}
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/shared.const.ts b/src/ui_ng/src/app/shared/shared.const.ts
index 3fef2028d..d7bd16a78 100644
--- a/src/ui_ng/src/app/shared/shared.const.ts
+++ b/src/ui_ng/src/app/shared/shared.const.ts
@@ -1,11 +1,23 @@
export const supportedLangs = ['en', 'zh'];
export const enLang = "en";
export const languageNames = {
- "en": "English",
- "zh": "中文简体"
+ "en": "English",
+ "zh": "中文简体"
};
export const enum AlertType {
DANGER, WARNING, INFO, SUCCESS
};
-export const dismissInterval = 15 * 1000;
\ No newline at end of file
+export const dismissInterval = 15 * 1000;
+export const httpStatusCode = {
+ "Unauthorized": 401,
+ "Forbidden": 403
+};
+export const enum DeletionTargets {
+ EMPTY, PROJECT, PROJECT_MEMBER, USER, POLICY, TARGET
+};
+export const harborRootRoute = "/harbor";
+
+export const enum ActionType {
+ ADD_NEW, EDIT
+};
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/shared.module.ts b/src/ui_ng/src/app/shared/shared.module.ts
index 6a43d326d..aa51e5190 100644
--- a/src/ui_ng/src/app/shared/shared.module.ts
+++ b/src/ui_ng/src/app/shared/shared.module.ts
@@ -15,7 +15,18 @@ import { RouterModule } from '@angular/router';
import { DeletionDialogComponent } from './deletion-dialog/deletion-dialog.component';
import { DeletionDialogService } from './deletion-dialog/deletion-dialog.service';
+import { BaseRoutingResolver } from './route/base-routing-resolver.service';
+import { SystemAdminGuard } from './route/system-admin-activate.service';
+import { NewUserFormComponent } from './new-user-form/new-user-form.component';
+import { InlineAlertComponent } from './inline-alert/inline-alert.component';
+import { ListPolicyComponent } from './list-policy/list-policy.component';
+import { CreateEditPolicyComponent } from './create-edit-policy/create-edit-policy.component';
+
+import { PortValidatorDirective } from './port.directive';
+
+import { PageNotFoundComponent } from './not-found/not-found.component';
+import { AboutDialogComponent } from './about-dialog/about-dialog.component';
@NgModule({
imports: [
@@ -28,7 +39,14 @@ import { DeletionDialogService } from './deletion-dialog/deletion-dialog.service
MaxLengthExtValidatorDirective,
FilterComponent,
HarborActionOverflow,
- DeletionDialogComponent
+ DeletionDialogComponent,
+ NewUserFormComponent,
+ InlineAlertComponent,
+ ListPolicyComponent,
+ CreateEditPolicyComponent,
+ PortValidatorDirective,
+ PageNotFoundComponent,
+ AboutDialogComponent
],
exports: [
CoreModule,
@@ -37,13 +55,22 @@ import { DeletionDialogService } from './deletion-dialog/deletion-dialog.service
FilterComponent,
HarborActionOverflow,
TranslateModule,
- DeletionDialogComponent
+ DeletionDialogComponent,
+ NewUserFormComponent,
+ InlineAlertComponent,
+ ListPolicyComponent,
+ CreateEditPolicyComponent,
+ PortValidatorDirective,
+ PageNotFoundComponent,
+ AboutDialogComponent
],
providers: [
SessionService,
MessageService,
CookieService,
- DeletionDialogService]
+ DeletionDialogService,
+ BaseRoutingResolver,
+ SystemAdminGuard]
})
export class SharedModule {
diff --git a/src/ui_ng/src/app/shared/shared.utils.ts b/src/ui_ng/src/app/shared/shared.utils.ts
new file mode 100644
index 000000000..5d6cde0a1
--- /dev/null
+++ b/src/ui_ng/src/app/shared/shared.utils.ts
@@ -0,0 +1,62 @@
+import { NgForm } from '@angular/forms';
+import { httpStatusCode, AlertType } from './shared.const';
+import { MessageService } from '../global-message/message.service';
+/**
+ * To handle the error message body
+ *
+ * @export
+ * @returns {string}
+ */
+export const errorHandler = function (error: any): string {
+ if (error) {
+ if (error.message) {
+ return error.message;
+ } else if (error._body) {
+ return error._body;
+ } else if (error.statusText) {
+ return error.statusText;
+ } else {
+ return error;
+ }
+ }
+
+ return "UNKNOWN_ERROR";
+}
+
+/**
+ * To check if form is empty
+ */
+export const isEmptyForm = function (ngForm: NgForm): boolean {
+ if (ngForm && ngForm.form) {
+ let values = ngForm.form.value;
+ if (values) {
+ for (var key in values) {
+ if (values[key]) {
+ return false;
+ }
+ }
+ }
+
+ }
+
+ return true;
+}
+
+/**
+ * Hanlde the 401 and 403 code
+ *
+ * If handled the 401 or 403, then return true otherwise false
+ */
+export const accessErrorHandler = function (error: any, msgService: MessageService): boolean {
+ if (error && error.status && msgService) {
+ if (error.status === httpStatusCode.Unauthorized) {
+ this.msgService.announceAppLevelMessage(error.status, "UNAUTHORIZED_ERROR", AlertType.DANGER);
+ return true;
+ } else if (error.status === httpStatusCode.Forbidden) {
+ this.msgService.announceAppLevelMessage(error.status, "FORBIDDEN_ERROR", AlertType.DANGER);
+ return true;
+ }
+ }
+
+ return false;
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/user/new-user-modal.component.html b/src/ui_ng/src/app/user/new-user-modal.component.html
index 2712c5509..23d5a2991 100644
--- a/src/ui_ng/src/app/user/new-user-modal.component.html
+++ b/src/ui_ng/src/app/user/new-user-modal.component.html
@@ -1,15 +1,8 @@
-
+
{{'USER.ADD_USER_TITLE' | translate}}
-
-
-
-
- {{errorMessage}}
-
-
-
+