mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-12 10:50:44 +01:00
Add project member component.
This commit is contained in:
parent
793416e4a7
commit
bb83763886
File diff suppressed because it is too large
Load Diff
@ -55,4 +55,4 @@
|
||||
"typings": "^1.4.0",
|
||||
"webdriver-manager": "10.2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
|
||||
const signInUrl = '/login';
|
||||
const url_prefix = '/ng';
|
||||
const signInUrl = url_prefix + '/login';
|
||||
/**
|
||||
*
|
||||
* Define a service to provide sign in methods
|
||||
|
@ -9,7 +9,7 @@
|
||||
<button class="action-item" (click)="onEdit(p)">Edit</button>
|
||||
<button class="action-item" (click)="onDelete(p)">Delete</button>
|
||||
</clr-dg-action-overflow>-->
|
||||
<clr-dg-cell><a [routerLink]="['/harbor', 'projects', p.id, 'repository']" >{{p.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell><a [routerLink]="['/harbor', 'projects', p.project_id, 'repository']" >{{p.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{p.public == 1 ? 'Public': 'Private'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.creation_time}}</clr-dg-cell>
|
||||
|
@ -0,0 +1,37 @@
|
||||
<clr-modal [(clrModalOpen)]="addMemberOpened">
|
||||
<h3 class="modal-title">Add Member</h3>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="member_name" class="col-md-4">Username</label>
|
||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="hasError" [class.valid]="!hasError" class="tooltip tooltip-validation tooltip-sm tooltip-bottom-right">
|
||||
<input type="text" id="member_name" [(ngModel)]="member.username" name="name" size="20" (keyup)="hasError=false;">
|
||||
<span class="tooltip-content">
|
||||
{{errorMessage}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-4">Role</label>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_project_admin" (click)="member.role_id = 1" [checked]="member.role_id === 1">
|
||||
<label for="checkrads_project_admin">Project Admin</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_developer" (click)="member.role_id = 2" [checked]="member.role_id === 2">
|
||||
<label for="checkrads_developer">Developer</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_guest" (click)="member.role_id = 3" [checked]="member.role_id === 3">
|
||||
<label for="checkrads_guest">Guest</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="addMemberOpened = false">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" (click)="onSubmit()">Ok</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,61 @@
|
||||
import { Component, Input, EventEmitter, Output } from '@angular/core';
|
||||
import { Response } from '@angular/http';
|
||||
import { MemberService } from '../member.service';
|
||||
import { MessageService } from '../../../global-message/message.service';
|
||||
import { Member } from '../member';
|
||||
|
||||
@Component({
|
||||
selector: 'add-member',
|
||||
templateUrl: 'add-member.component.html'
|
||||
})
|
||||
export class AddMemberComponent {
|
||||
|
||||
member: Member = new Member();
|
||||
addMemberOpened: boolean;
|
||||
errorMessage: string;
|
||||
hasError: boolean;
|
||||
|
||||
@Input() projectId: number;
|
||||
@Output() added = new EventEmitter<boolean>();
|
||||
|
||||
constructor(private memberService: MemberService, private messageService: MessageService) {}
|
||||
|
||||
onSubmit(): void {
|
||||
this.hasError = false;
|
||||
console.log('Adding member:' + JSON.stringify(this.member));
|
||||
this.memberService
|
||||
.addMember(this.projectId, this.member.username, this.member.role_id)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Added member successfully.');
|
||||
this.added.emit(true);
|
||||
this.addMemberOpened = false;
|
||||
},
|
||||
error=>{
|
||||
this.hasError = true;
|
||||
if (error instanceof Response) {
|
||||
switch(error.status){
|
||||
case 404:
|
||||
this.errorMessage = 'Username does not exist.';
|
||||
break;
|
||||
case 409:
|
||||
this.errorMessage = 'Username already exists.';
|
||||
break;
|
||||
default:
|
||||
this.errorMessage = 'Unknow error occurred while adding member.';
|
||||
this.messageService.announceMessage(this.errorMessage);
|
||||
}
|
||||
}
|
||||
console.log('Failed to add member of project:' + this.projectId, ' with error:' + error);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
openAddMemberModal(): void {
|
||||
this.hasError = false;
|
||||
this.member = new Member();
|
||||
this.addMemberOpened = true;
|
||||
}
|
||||
}
|
@ -2,10 +2,11 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="col-xs-4">
|
||||
<button class="btn btn-sm">new user</button>
|
||||
<button class="btn btn-sm" (click)="openAddMemberModal()">new user</button>
|
||||
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<input type="text" placeholder="Search for users">
|
||||
<input type="text" placeholder="Search for users" #searchMember (keyup.enter)="doSearch(searchMember.value)">
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid>
|
||||
@ -13,23 +14,25 @@
|
||||
<clr-dg-column>Role</clr-dg-column>
|
||||
<clr-dg-column>Action</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let u of members">
|
||||
<clr-dg-cell>{{u.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{u.role}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{u.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{roleInfo[u.role_id]}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'">
|
||||
<clr-dropdown [clrMenuPosition]="'bottom-left'" [hidden]="u.user_id === currentUser.user_id">
|
||||
<button class="btn btn-sm btn-link" clrDropdownToggle>
|
||||
Actions
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="javascript:void(0)" clrDropdownItem>Project Admin</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Developer</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem>Guest</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 1)">Project Admin</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 2)">Developer</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="changeRole(u.user_id, 3)">Guest</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)="deleteMember(u.user_id)">Delete Member</a>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{members.length}} item(s)</clr-dg-footer>
|
||||
<clr-dg-footer>{{ (members ? members.length : 0) }} item(s)</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
@ -1,18 +1,90 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { Member } from './member';
|
||||
import { MemberService } from './member.service';
|
||||
|
||||
import { AddMemberComponent } from './add-member/add-member.component';
|
||||
|
||||
import { MessageService } from '../../global-message/message.service';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
export const roleInfo: {} = { 1: 'ProjectAdmin', 2: 'Developer', 3: 'Guest'};
|
||||
|
||||
@Component({
|
||||
templateUrl: 'member.component.html'
|
||||
})
|
||||
export class MemberComponent implements OnInit {
|
||||
|
||||
currentUser: SessionUser;
|
||||
members: Member[];
|
||||
projectId: number;
|
||||
roleInfo = roleInfo;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.members = [
|
||||
{ name: 'Admin', role: 'Sys admin'},
|
||||
{ name: 'user01', role: 'Project Admin'},
|
||||
{ name: 'user02', role: 'Developer'},
|
||||
{ name: 'user03', role: 'Guest'}
|
||||
];
|
||||
@ViewChild(AddMemberComponent)
|
||||
addMemberComponent: AddMemberComponent;
|
||||
|
||||
constructor(private route: ActivatedRoute, private memberService: MemberService, private messageService: MessageService) {
|
||||
//Get current user from registered resolver.
|
||||
this.route.data.subscribe(data=>this.currentUser = <SessionUser>data['memberResolver']);
|
||||
}
|
||||
|
||||
retrieve(projectId:number, username: string) {
|
||||
this.memberService
|
||||
.listMembers(projectId, username)
|
||||
.subscribe(
|
||||
response=>this.members = response,
|
||||
error=>console.log(error)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
//Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
console.log('Get projectId from route params snapshot:' + this.projectId);
|
||||
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
|
||||
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('Failed to change role with user ' + userId + ' to roleId ' + roleId)
|
||||
);
|
||||
}
|
||||
|
||||
deleteMember(userId: number) {
|
||||
this.memberService
|
||||
.deleteMember(this.projectId, userId)
|
||||
.subscribe(
|
||||
response=>{
|
||||
console.log('Successful change role with user ' + userId);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage('Failed to change role with user ' + userId)
|
||||
);
|
||||
}
|
||||
|
||||
doSearch(searchMember) {
|
||||
this.retrieve(this.projectId, searchMember);
|
||||
}
|
||||
}
|
52
harbor-app/src/app/project/member/member.service.ts
Normal file
52
harbor-app/src/app/project/member/member.service.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
import { BaseService } from '../../service/base.service';
|
||||
import { Member } from './member';
|
||||
|
||||
export const urlPrefix = '/ng';
|
||||
|
||||
@Injectable()
|
||||
export class MemberService extends BaseService {
|
||||
|
||||
constructor(private http: Http) {
|
||||
super();
|
||||
}
|
||||
|
||||
listMembers(projectId: number, username: string): Observable<Member[]> {
|
||||
console.log('Get member from project_id:' + projectId + ', username:' + username);
|
||||
return this.http
|
||||
.get(urlPrefix + `/api/projects/${projectId}/members?username=${username}`)
|
||||
.map(response=>response.json())
|
||||
.catch(error=>this.handleError(error));
|
||||
}
|
||||
|
||||
addMember(projectId: number, username: string, roleId: number): Observable<any> {
|
||||
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 ] })
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
changeMemberRole(projectId: number, userId: number, roleId: number): Observable<any> {
|
||||
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 ]})
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
deleteMember(projectId: number, userId: number): Observable<any> {
|
||||
console.log('Deleting member role with userId:' + userId + ' under projectId:' + projectId);
|
||||
return this.http
|
||||
.delete(urlPrefix + `/api/projects/${projectId}/members/${userId}`)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
}
|
@ -1,4 +1,25 @@
|
||||
/*
|
||||
{
|
||||
"user_id": 1,
|
||||
"username": "admin",
|
||||
"email": "",
|
||||
"password": "",
|
||||
"realname": "",
|
||||
"comment": "",
|
||||
"deleted": 0,
|
||||
"role_name": "projectAdmin",
|
||||
"role_id": 1,
|
||||
"has_admin_role": 0,
|
||||
"reset_uuid": "",
|
||||
"creation_time": "0001-01-01T00:00:00Z",
|
||||
"update_time": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
*/
|
||||
|
||||
export class Member {
|
||||
name: string;
|
||||
role: string;
|
||||
user_id: number;
|
||||
username: string;
|
||||
role_name: string;
|
||||
has_admin_role: number;
|
||||
role_id: number;
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'project-detail',
|
||||
templateUrl: "project-detail.component.html",
|
||||
styleUrls: [ 'project-detail.css' ]
|
||||
})
|
||||
export class ProjectDetailComponent {
|
||||
// constructor(private router: Router){}
|
||||
}
|
||||
export class ProjectDetailComponent {}
|
@ -36,7 +36,12 @@ const projectRoutes: Routes = [
|
||||
children: [
|
||||
{ path: 'repository', component: RepositoryComponent },
|
||||
{ path: 'replication', component: ReplicationComponent },
|
||||
{ path: 'member', component: MemberComponent },
|
||||
{
|
||||
path: 'member', component: MemberComponent,
|
||||
resolve: {
|
||||
memberResolver: BaseRoutingResolver
|
||||
}
|
||||
},
|
||||
{ path: 'log', component: AuditLogComponent }
|
||||
]
|
||||
}
|
||||
|
@ -14,10 +14,11 @@ 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 { DATAGRID_DIRECTIVES } from 'clarity-angular';
|
||||
import { MemberService } from './member/member.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -35,10 +36,11 @@ import { DATAGRID_DIRECTIVES } from 'clarity-angular';
|
||||
ActionProjectComponent,
|
||||
ListProjectComponent,
|
||||
ProjectDetailComponent,
|
||||
MemberComponent
|
||||
MemberComponent,
|
||||
AddMemberComponent
|
||||
],
|
||||
exports: [ ListProjectComponent ],
|
||||
providers: [ ProjectService ]
|
||||
providers: [ ProjectService, MemberService ]
|
||||
})
|
||||
export class ProjectModule {
|
||||
|
||||
|
@ -10,6 +10,8 @@ import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
const url_prefix = '/ng';
|
||||
|
||||
@Injectable()
|
||||
export class ProjectService extends BaseService {
|
||||
|
||||
@ -22,14 +24,14 @@ export class ProjectService extends BaseService {
|
||||
|
||||
listProjects(name: string, isPublic: number): Observable<Project[]>{
|
||||
return this.http
|
||||
.get(`/ng/api/projects?project_name=${name}&is_public=${isPublic}`, this.options)
|
||||
.get(url_prefix + `/api/projects?project_name=${name}&is_public=${isPublic}`, this.options)
|
||||
.map(response=>response.json())
|
||||
.catch(this.handleError);
|
||||
}
|
||||
|
||||
createProject(name: string, isPublic: number): Observable<any> {
|
||||
return this.http
|
||||
.post(`/ng/api/projects`,
|
||||
.post(url_prefix + `/api/projects`,
|
||||
JSON.stringify({'project_name': name, 'public': isPublic})
|
||||
, this.options)
|
||||
.map(response=>response.status)
|
||||
@ -38,14 +40,14 @@ export class ProjectService extends BaseService {
|
||||
|
||||
toggleProjectPublic(projectId: number, isPublic: number): Observable<any> {
|
||||
return this.http
|
||||
.put(`/ng/api/projects/${projectId}/publicity`, { 'public': isPublic }, this.options)
|
||||
.put(url_prefix + `/api/projects/${projectId}/publicity`, { 'public': isPublic }, this.options)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
deleteProject(projectId: number): Observable<any> {
|
||||
return this.http
|
||||
.delete(`/ng/api/projects/${projectId}`)
|
||||
.delete(url_prefix + `/api/projects/${projectId}`)
|
||||
.map(response=>response.status)
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
@ -5,10 +5,12 @@ import 'rxjs/add/operator/toPromise';
|
||||
import { SessionUser } from './session-user';
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
|
||||
const signInUrl = '/login';
|
||||
const currentUserEndpint = "/api/users/current";
|
||||
const signOffEndpoint = "/log_out";
|
||||
const accountEndpoint = "/api/users/:id";
|
||||
const urlPrefix = '/ng';
|
||||
const signInUrl = urlPrefix + '/login';
|
||||
const currentUserEndpint = urlPrefix + "/api/users/current";
|
||||
const signOffEndpoint = urlPrefix + "/log_out";
|
||||
const accountEndpoint = urlPrefix + "/api/users/:id";
|
||||
|
||||
/**
|
||||
* Define related methods to handle account and session corresponding things
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user