Merge pull request #1770 from wknet123/dev-revised

Fix issues per testing and merge latest updates.
This commit is contained in:
Steven Zou 2017-03-24 11:00:46 +08:00 committed by GitHub
commit 02c3d4d623
33 changed files with 291 additions and 215 deletions

View File

@ -0,0 +1,21 @@
<!--
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html>
<html>
<body>
<p>Please click this link to reset your password:</p>
<a href="{{.URL}}/reset_password?reset_uuid={{.UUID}}">{{.URL}}/reset_password?reset_uuid={{.UUID}}</a>
</body>
</html>

View File

@ -76,7 +76,10 @@ const harborRoutes: Routes = [
},
{
path: 'tags/:id/:repo',
component: TagRepositoryComponent
component: TagRepositoryComponent,
resolve: {
projectResolver: ProjectRoutingResolver
}
},
{
path: 'projects/:id',

View File

@ -1,8 +1,6 @@
import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions } from '@angular/http';
import { BaseService } from '../service/base.service';
import { AuditLog } from './audit-log';
import { Observable } from 'rxjs/Observable';
@ -13,7 +11,7 @@ import 'rxjs/add/observable/throw';
export const logEndpoint = "/api/logs";
@Injectable()
export class AuditLogService extends BaseService {
export class AuditLogService {
private httpOptions = new RequestOptions({
headers: new Headers({
"Content-Type": 'application/json',
@ -21,9 +19,7 @@ export class AuditLogService extends BaseService {
})
});
constructor(private http: Http) {
super();
}
constructor(private http: Http) {}
listAuditLogs(queryParam: AuditLog): Observable<any> {
return this.http
@ -36,12 +32,12 @@ export class AuditLogService extends BaseService {
username: queryParam.username
})
.map(response => response)
.catch(error => this.handleError(error));
.catch(error => Observable.throw(error));
}
getRecentLogs(lines: number): Observable<AuditLog[]> {
return this.http.get(logEndpoint + "?lines=" + lines, this.httpOptions)
.map(response => response.json() as AuditLog[])
.catch(error => this.handleError(error));
.catch(error => Observable.throw(error));
}
}

View File

@ -31,6 +31,6 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="!projectForm.form.valid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="projectForm.form.invalid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
</div>
</clr-modal>

View File

@ -1,9 +1,9 @@
<clr-datagrid (clrDgRefresh)="refresh($event)">
<clr-dg-column>{{'PROJECT.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</clr-dg-column>
<clr-dg-column *ngIf="showRoleInfo">{{'PROJECT.ROLE' | translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.DESCRIPTION' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
<clr-dg-action-overflow [hidden]="!listFullMode || p.current_user_role_id !== 1">
<button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
@ -12,9 +12,9 @@
</clr-dg-action-overflow>
<clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell>
<clr-dg-cell>{{ (p.public === 1 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell>
<clr-dg-cell *ngIf="showRoleInfo">{{roleInfo[p.current_user_role_id] | translate}}</clr-dg-cell>
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
<clr-dg-cell>{{p.creation_time}}</clr-dg-cell>
<clr-dg-cell>{{p.description}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}

View File

@ -5,7 +5,7 @@ import { ProjectService } from '../project.service';
import { SessionService } from '../../shared/session.service';
import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
import { ListMode } from '../../shared/shared.const';
import { ListMode, ProjectTypes, RoleInfo } from '../../shared/shared.const';
import { State } from 'clarity-angular';
@ -24,6 +24,8 @@ export class ListProjectComponent implements OnInit {
@Input() totalRecordCount: number;
pageOffset: number = 1;
@Input() filteredType: string;
@Output() paginate = new EventEmitter<State>();
@Output() toggle = new EventEmitter<Project>();
@ -31,6 +33,8 @@ export class ListProjectComponent implements OnInit {
@Input() mode: string = ListMode.FULL;
roleInfo = RoleInfo;
constructor(
private session: SessionService,
private router: Router,
@ -43,6 +47,10 @@ export class ListProjectComponent implements OnInit {
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
}
get showRoleInfo(): boolean {
return this.listFullMode && this.filteredType === ProjectTypes[0];
}
public get isSystemAdmin(): boolean {
let account = this.session.getCurrentUser();
return account != null && account.has_admin_role > 0;

View File

@ -36,6 +36,6 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="!memberForm.form.valid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="memberForm.form.invalid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
</div>
</clr-modal>

View File

@ -17,15 +17,15 @@
<clr-datagrid>
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let u of members">
<clr-dg-action-overflow [hidden]="u.user_id === currentUser.user_id || !hasProjectAdminRole">
<button class="action-item" (click)="changeRole(u.user_id, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
<button class="action-item" (click)="changeRole(u.user_id, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
<button class="action-item" (click)="changeRole(u.user_id, 3)">{{'MEMBER.GUEST' | translate}}</button>
<button class="action-item" (click)="deleteMember(u.user_id)">{{'MEMBER.DELETE' | translate}}</button>
<clr-dg-row *ngFor="let m of members">
<clr-dg-action-overflow [hidden]="m.user_id === currentUser.user_id || !hasProjectAdminRole">
<button class="action-item" (click)="changeRole(m, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
<button class="action-item" (click)="changeRole(m, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
<button class="action-item" (click)="changeRole(m, 3)">{{'MEMBER.GUEST' | translate}}</button>
<button class="action-item" (click)="deleteMember(m)">{{'MEMBER.DELETE' | translate}}</button>
</clr-dg-action-overflow>
<clr-dg-cell>{{u.username}}</clr-dg-cell>
<clr-dg-cell>{{roleInfo[u.role_id] | translate}}</clr-dg-cell>
<clr-dg-cell>{{m.username}}</clr-dg-cell>
<clr-dg-cell>{{roleInfo[m.role_id] | translate}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer>
</clr-datagrid>

View File

@ -15,6 +15,8 @@ import { ConfirmationDialogService } from '../../shared/confirmation-dialog/conf
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
import { SessionService } from '../../shared/session.service';
import { RoleInfo } from '../../shared/shared.const';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/catch';
@ -22,7 +24,7 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
import { Subscription } from 'rxjs/Subscription';
export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };
import { Project } from '../../project/project';
@Component({
moduleId: module.id,
@ -31,31 +33,22 @@ export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER',
})
export class MemberComponent implements OnInit, OnDestroy {
currentUser: SessionUser;
members: Member[];
projectId: number;
roleInfo = roleInfo;
roleInfo = RoleInfo;
private delSub: Subscription;
@ViewChild(AddMemberComponent)
addMemberComponent: AddMemberComponent;
currentUser: SessionUser;
hasProjectAdminRole: boolean;
constructor(private route: ActivatedRoute, private router: Router,
private memberService: MemberService, private messageService: MessageService,
private deletionDialogService: ConfirmationDialogService,
session: SessionService) {
//Get current user from registered resolver.
this.currentUser = session.getCurrentUser();
let projectMembers: Member[] = session.getProjectMembers();
if(this.currentUser && projectMembers) {
let currentMember = projectMembers.find(m=>m.user_id === this.currentUser.user_id);
if(currentMember) {
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
}
}
private session: SessionService) {
this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => {
if (message &&
message.state === ConfirmationState.CONFIRMED &&
@ -82,8 +75,7 @@ export class MemberComponent implements OnInit, OnDestroy {
error => {
this.router.navigate(['/harbor', 'projects']);
this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
}
);
});
}
ngOnDestroy() {
@ -97,6 +89,15 @@ export class MemberComponent implements OnInit, OnDestroy {
this.projectId = +this.route.snapshot.parent.params['id'];
console.log('Get projectId from route params snapshot:' + this.projectId);
this.currentUser = this.session.getCurrentUser();
//Get current user from registered resolver.
let resolverData = this.route.snapshot.parent.data;
if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
}
this.retrieve(this.projectId, '');
}
@ -108,25 +109,27 @@ export class MemberComponent implements OnInit, OnDestroy {
this.retrieve(this.projectId, '');
}
changeRole(userId: number, roleId: number) {
this.memberService
.changeMemberRole(this.projectId, userId, roleId)
.subscribe(
response => {
this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS);
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)
);
changeRole(m: Member, roleId: number) {
if(m) {
this.memberService
.changeMemberRole(this.projectId, m.user_id, roleId)
.subscribe(
response => {
this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS);
console.log('Successful change role with user ' + m.user_id + ' to roleId ' + roleId);
this.retrieve(this.projectId, '');
},
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + m.user_id + ' to roleId ' + roleId, AlertType.DANGER)
);
}
}
deleteMember(userId: number) {
deleteMember(m: Member) {
let deletionMessage: ConfirmationMessage = new ConfirmationMessage(
'MEMBER.DELETION_TITLE',
'MEMBER.DELETION_SUMMARY',
userId + "",
userId,
m.username,
m.user_id,
ConfirmationTargets.PROJECT_MEMBER
);
this.deletionDialogService.openComfirmDialog(deletionMessage);

View File

@ -6,22 +6,19 @@ 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';
@Injectable()
export class MemberService extends BaseService {
export class MemberService {
constructor(private http: Http) {
super();
}
constructor(private http: Http) {}
listMembers(projectId: number, username: string): Observable<Member[]> {
console.log('Get member from project_id:' + projectId + ', username:' + username);
return this.http
.get(`/api/projects/${projectId}/members?username=${username}`)
.map(response=>response.json())
.catch(error=>this.handleError(error));
.map(response=>response.json() as Member[])
.catch(error=>Observable.throw(error));
}
addMember(projectId: number, username: string, roleId: number): Observable<any> {

View File

@ -5,10 +5,10 @@
<li class="nav-item">
<a class="nav-link" routerLink="repository" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSessionValid">
<li class="nav-item" *ngIf="isSystemAdmin || isMember">
<a class="nav-link" routerLink="member" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSessionValid">
<li class="nav-item" *ngIf="isSystemAdmin || isMember">
<a class="nav-link" routerLink="log" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSessionValid && isSystemAdmin">

View File

@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Project } from '../project';
import { SessionService } from '../../shared/session.service';
import { ProjectService } from '../../project/project.service';
@Component({
selector: 'project-detail',
@ -13,13 +14,18 @@ import { SessionService } from '../../shared/session.service';
export class ProjectDetailComponent {
currentProject: Project;
isMember: boolean;
constructor(
private route: ActivatedRoute,
private router: Router,
private sessionService: SessionService) {
this.route.data.subscribe(data=>this.currentProject = <Project>data['projectResolver']);
private sessionService: SessionService,
private projectService: ProjectService) {
this.route.data.subscribe(data=>{
this.currentProject = <Project>data['projectResolver'];
this.isMember = this.currentProject.is_member;
});
}
public get isSystemAdmin(): boolean {

View File

@ -3,23 +3,43 @@ import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@a
import { Project } from './project';
import { ProjectService } from './project.service';
import { SessionService } from '../shared/session.service';
import 'rxjs/add/operator/mergeMap';
@Injectable()
export class ProjectRoutingResolver implements Resolve<Project>{
constructor(private projectService: ProjectService, private router: Router) {}
constructor(
private sessionService: SessionService,
private projectService: ProjectService,
private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Project> {
let projectId = route.params['id'];
let projectId = route.params['id'];
console.log('Project resolver, projectID:' + projectId);
return this.projectService
.getProject(projectId)
.then(project=> {
if(project) {
return project;
} else {
this.router.navigate(['/harbor', 'projects']);
return null;
}
.toPromise()
.then((project: Project)=> {
if(project) {
let currentUser = this.sessionService.getCurrentUser();
let projectMembers = this.sessionService.getProjectMembers();
if(currentUser && projectMembers) {
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
if(currentMember) {
project.is_member = true;
project.has_project_admin_role = (currentMember.role_name === 'projectAdmin') || currentUser.has_admin_role === 1;
}
}
return project;
} else {
this.router.navigate(['/harbor', 'projects']);
return null;
}
}).catch(error=>{
this.router.navigate(['/harbor', 'projects']);
return null;
});
}
}

View File

@ -1,11 +1,13 @@
.header-title {
margin-top: 0;
margin-top: 12px;
}
.option-left {
padding-left: 12px;
margin-top: 12px;
padding-left: 12px;
margin-top: 12px;
}
.option-right {
padding-right: 16px;
margin-top: 18px;
}
padding-right: 16px;
margin-top: 18px;
}

View File

@ -1,6 +1,9 @@
<div class="row">
<div class="row" style="margin-right: 12px;">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
<div>
<statistics-panel></statistics-panel>
</div>
<div class="row flex-items-xs-between">
<div class="option-left">
<button *ngIf="projectCreationRestriction" class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.PROJECT' | translate}}</button>
@ -18,11 +21,13 @@
</div>
</clr-dropdown>
<grid-filter filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchProjects($event)"></grid-filter>
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a>
<a href="javascript:void(0)" (click)="refresh()">
<clr-icon shape="refresh"></clr-icon>
</a>
</div>
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<list-project [projects]="changedProjects" (toggle)="toggleProject($event)" (delete)="deleteProject($event)" (paginate)="retrieve($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount"></list-project>
</div>
<list-project [projects]="changedProjects" [filteredType]="projectTypes[currentFilteredType]" (toggle)="toggleProject($event)" (delete)="deleteProject($event)" (paginate)="retrieve($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount"></list-project>
</div>
</div>

View File

@ -25,9 +25,7 @@ import { State } from 'clarity-angular';
import { AppConfigService } from '../app-config.service';
import { SessionService } from '../shared/session.service';
const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
import { ProjectTypes } from '../shared/shared.const';
@Component({
moduleId: module.id,
@ -39,7 +37,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
selected = [];
changedProjects: Project[];
projectTypes = types;
projectTypes = ProjectTypes;
@ViewChild(CreateProjectComponent)
creationProject: CreateProjectComponent;
@ -145,7 +143,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
}
doFilterProjects(filteredType: number): void {
console.log('Filter projects with type:' + types[filteredType]);
console.log('Filter projects with type:' + this.projectTypes[filteredType]);
this.isPublic = filteredType;
this.currentFilteredType = filteredType;
this.retrieve();

View File

@ -3,8 +3,6 @@ import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions, Response, URLSearchParams } from '@angular/http';
import { Project } from './project';
import { BaseService } from '../service/base.service';
import { Message } from '../global-message/message';
import { Observable } from 'rxjs/Observable';
@ -22,11 +20,10 @@ export class ProjectService {
constructor(private http: Http) {}
getProject(projectId: number): Promise<Project> {
getProject(projectId: number): Observable<any> {
return this.http
.get(`/api/projects/${projectId}`)
.toPromise()
.then(response=>response.json() as Project)
.map(response=>response.json())
.catch(error=>Observable.throw(error));
}

View File

@ -29,4 +29,6 @@ export class Project {
update_time: Date;
current_user_role_id: number;
repo_count: number;
has_project_admin_role: boolean;
is_member: boolean;
}

View File

@ -1,8 +1,6 @@
import { Injectable } from '@angular/core';
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';
@ -14,10 +12,8 @@ import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/mergeMap';
@Injectable()
export class ReplicationService extends BaseService {
constructor(private http: Http) {
super();
}
export class ReplicationService {
constructor(private http: Http) {}
listPolicies(policyName: string, projectId?: any): Observable<Policy[]> {
if(!projectId) {

View File

@ -1,4 +1,4 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';
import { Repository } from '../repository';
import { State } from 'clarity-angular';
@ -8,16 +8,17 @@ import { SessionService } from '../../shared/session.service';
import { ListMode } from '../../shared/shared.const';
import { SessionUser } from '../../shared/session-user';
import { Member } from '../../project/member/member';
@Component({
selector: 'list-repository',
templateUrl: 'list-repository.component.html'
})
export class ListRepositoryComponent {
export class ListRepositoryComponent implements OnInit {
@Input() projectId: number;
@Input() repositories: Repository[];
@Output() delete = new EventEmitter<string>();
@Input() totalPage: number;
@ -25,25 +26,16 @@ export class ListRepositoryComponent {
@Output() paginate = new EventEmitter<State>();
@Input() mode: string = ListMode.FULL;
@Input() hasProjectAdminRole: boolean;
pageOffset: number = 1;
hasProjectAdminRole: boolean;
constructor(
private router: Router,
private searchTrigger: SearchTriggerService,
private session: SessionService) {
//Get current user from registered resolver.
let currentUser = session.getCurrentUser();
let projectMembers: Member[] = session.getProjectMembers();
if(currentUser && projectMembers) {
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
if(currentMember) {
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
}
}
}
private session: SessionService) { }
ngOnInit() {}
deleteRepo(repoName: string) {
this.delete.emit(repoName);

View File

@ -8,6 +8,6 @@
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<list-repository [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount" (paginate)="retrieve($event)"></list-repository>
<list-repository [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount" [hasProjectAdminRole]="hasProjectAdminRole" (paginate)="retrieve($event)"></list-repository>
</div>
</div>

View File

@ -14,6 +14,8 @@ import { Subscription } from 'rxjs/Subscription';
import { State } from 'clarity-angular';
import { Project } from '../project/project';
const repositoryTypes = [
{ key: '0', description: 'REPOSITORY.MY_REPOSITORY' },
{ key: '1', description: 'REPOSITORY.PUBLIC_REPOSITORY' }
@ -39,6 +41,8 @@ export class RepositoryComponent implements OnInit {
totalPage: number;
totalRecordCount: number;
hasProjectAdminRole: boolean;
subscription: Subscription;
constructor(
@ -66,12 +70,16 @@ export class RepositoryComponent implements OnInit {
error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER)
);
}
}
);
});
}
ngOnInit(): void {
this.projectId = this.route.snapshot.parent.params['id'];
let resolverData = this.route.snapshot.parent.data;
if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
}
this.currentRepositoryType = this.repositoryTypes[0];
this.lastFilteredRepoName = '';
this.retrieve();

View File

@ -16,7 +16,8 @@ import { TagView } from '../tag-view';
import { AppConfigService } from '../../app-config.service';
import { SessionService } from '../../shared/session.service';
import { Member } from '../../project/member/member';
import { Project } from '../../project/project';
@Component({
moduleId: module.id,
@ -29,7 +30,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
projectId: number;
repoName: string;
hasProjectAdminRole: boolean;
hasProjectAdminRole: boolean = false;
tags: TagView[];
registryUrl: string;
@ -45,15 +46,6 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
private appConfigService: AppConfigService,
private session: SessionService){
let currentUser = session.getCurrentUser();
let projectMembers: Member[] = session.getProjectMembers();
if(currentUser && projectMembers) {
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
if(currentMember) {
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
}
}
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(
message => {
if (message &&
@ -78,11 +70,15 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
}
}
}
}
)
});
}
ngOnInit() {
let resolverData = this.route.snapshot.data;
console.log(JSON.stringify(resolverData));
if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
}
this.projectId = this.route.snapshot.params['id'];
this.repoName = this.route.snapshot.params['repo'];
this.tags = [];
@ -100,17 +96,17 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
retrieve() {
this.tags = [];
if(this.withNotary) {
this.repositoryService
.listTagsWithVerifiedSignatures(this.repoName)
.subscribe(
items => this.listTags(items),
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
this.repositoryService
.listTagsWithVerifiedSignatures(this.repoName)
.subscribe(
items => this.listTags(items),
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
} else {
this.repositoryService
.listTags(this.repoName)
.subscribe(
items => this.listTags(items),
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
.listTags(this.repoName)
.subscribe(
items => this.listTags(items),
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
}
}

View File

@ -1,8 +0,0 @@
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
export class AuthGuard implements CanActivate {
canActivate() {
return true;
}
}

View File

@ -1,18 +0,0 @@
import { Http, Response,} from '@angular/http';
export class BaseService {
protected handleError(error: Response | any): Promise<any> {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
console.log(typeof error);
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
return Promise.reject(error);
}
}

View File

@ -17,7 +17,8 @@ export class MemberGuard implements CanActivate, CanActivateChild {
private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
let projectId: number = route.params['id'];
let projectId = route.params['id'];
this.sessionService.setProjectMembers([]);
return new Promise((resolve, reject) => {
this.projectService.checkProjectMember(projectId)
.subscribe(
@ -26,6 +27,10 @@ export class MemberGuard implements CanActivate, CanActivateChild {
return resolve(true)
},
error => {
//Add exception for repository in project detail router activation.
if(state.url.endsWith('repository')) {
return resolve(true);
}
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
return resolve(false);
});

View File

@ -52,4 +52,7 @@ export const CookieKeyOfAdmiral = "admiral.endpoint.latest";
export const enum ConfirmationState {
NA, CONFIRMED, CANCEL
}
}
export const ProjectTypes = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };

View File

@ -1,25 +1,41 @@
<div class="card card-block">
<h3 class="card-title">{{'STATISTICS.TITLE' | translate }}</h3>
<span class="card-text">
<div class="row">
<div class="col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<span class="statistic-column-title">{{'STATISTICS.PRO_ITEM' | translate }}</span>
</div>
<div class="col-xs-10 col-sm-10 col-md-10 col-lg-10 col-xl-10">
<statistics [data]='{number: originalCopy.my_project_count, label: "my"}'></statistics>
<statistics [data]='{number: originalCopy.public_project_count, label: "pub"}'></statistics>
<statistics [data]='{number: originalCopy.total_project_count, label: "total"}' *ngIf="isValidSession"></statistics>
</div>
</div>
<div class="row">
<div class="col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<span class="statistic-column-title">{{'STATISTICS.REPO_ITEM' | translate }}</span>
<div class="row flex-items-xs-between flex-items-xs-middle">
<div></div>
<div id="right_statistic_panel" style="margin-right: 18px;">
<div class="statistic-block">
<div class="statistic-column-block">
<div>
<span class="statistic-column-title statistic-column-title-pro">{{'STATISTICS.PRO_ITEM' | translate }}</span>
</div>
<div>
<span class="statistic-column-title statistic-column-title-repo">{{'STATISTICS.REPO_ITEM' | translate }}</span>
</div>
</div>
<div class="statistic-column-block" style="margin-left: 16px;">
<div>
<statistics [data]='originalCopy.my_project_count' [label]='"STATISTICS.INDEX_MY" | translate'></statistics>
</div>
<div>
<statistics [data]='originalCopy.my_repo_count' [label]='"STATISTICS.INDEX_MY" | translate'></statistics>
</div>
</div>
<div class="statistic-column-block" style="margin-left: 28px;">
<div>
<statistics [data]='originalCopy.public_project_count' [label]='"STATISTICS.INDEX_PUB" | translate'></statistics>
</div>
<div>
<statistics [data]='originalCopy.public_repo_count' [label]='"STATISTICS.INDEX_PUB" | translate'></statistics>
</div>
</div>
<div class="statistic-column-block" style="margin-left: 28px;">
<div>
<statistics [data]='originalCopy.total_project_count' [label]='"STATISTICS.INDEX_TOTAL" | translate' *ngIf="isValidSession"></statistics>
</div>
<div>
<statistics [data]='originalCopy.total_repo_count' [label]='"STATISTICS.INDEX_TOTAL" | translate' *ngIf="isValidSession"></statistics>
</div>
</div>
</div>
<div class="statistic-item-divider"></div>
<div class="statistic-block">Storage</div>
</div>
<div class="col-xs-10 col-sm-10 col-md-10 col-lg-10 col-xl-10">
<statistics [data]='{number: originalCopy.my_repo_count, label: "my"}'></statistics>
<statistics [data]='{number: originalCopy.public_repo_count, label: "pub"}'></statistics>
<statistics [data]='{number: originalCopy.total_repo_count, label: "total"}' *ngIf="isValidSession"></statistics>
</div>
</div>
</span>
</div>

View File

@ -1,30 +1,57 @@
.statistic-wrapper {
padding: 12px;
margin: 12px;
text-align: center;
padding: 4px;
margin: 4px;
text-align: right;
vertical-align: middle;
height: 72px;
min-width: 108px;
max-width: 216px;
height: 30px;
display: inline-block;
}
.statistic-data {
font-size: 48px;
font-weight: bolder;
font-family: "Metropolis";
line-height: 48px;
font-size: 16px;
font-weight: 900;
font-family: "semibold";
line-height: 16px;
}
.statistic-text {
font-size: 24px;
font-weight: 400;
line-height: 24px;
font-size: 10px;
line-height: 10px;
text-transform: uppercase;
font-family: "Metropolis";
font-family: "semibold";
}
.statistic-column-block {
display: inline-block;
text-align: right;
}
.statistic-column-title {
position: relative;
top: 40%;
text-transform: uppercase;
font-size: 14px;
}
.statistic-column-title-pro {
top: -10px;
}
.statistic-column-title-repo {
top: 3px;
}
.statistic-item-divider {
height: 54px;
display: inline-block;
width: 1px;
background-color: #ccc;
opacity: 0.55;
margin-left: 4px;
margin-right: 12px;
position: relative;
top: 3px;
}
.statistic-block {
display: inline-block;
}

View File

@ -1,4 +1,4 @@
<div class="statistic-wrapper">
<span class="statistic-data">{{data.number}}</span>
<span class="statistic-text">{{data.label}}</span>
<span class="statistic-data">{{data}}</span>
<span class="statistic-text">{{label}}</span>
</div>

View File

@ -7,5 +7,6 @@ import { Component, Input } from '@angular/core';
})
export class StatisticsComponent {
@Input() data: any;
@Input() label: string;
@Input() data: number = 0;
}

View File

@ -110,10 +110,10 @@
"PROJECT": {
"PROJECTS": "Projects",
"NAME": "Project Name",
"ROLE": "Role",
"PUBLIC_OR_PRIVATE": "Public",
"REPO_COUNT": "Repositories Count",
"CREATION_TIME": "Creation Time",
"DESCRIPTION": "Description",
"PUBLIC": "Public",
"PRIVATE": "Private",
"MAKE": "Make",
@ -290,7 +290,7 @@
"DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?",
"DELETION_TITLE_TAG_DENIED": "Signed Tag can't be deleted",
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted. {{param}}",
"FILTER_FOR_REPOSITORIES": "Filter for repositories",
"FILTER_FOR_REPOSITORIES": "Filter Repositories",
"TAG": "Tag",
"SIGNED": "Signed",
"AUTHOR": "Author",

View File

@ -110,10 +110,10 @@
"PROJECT": {
"PROJECTS": "项目",
"NAME": "项目名称",
"ROLE": "角色",
"PUBLIC_OR_PRIVATE": "公开",
"REPO_COUNT": "镜像仓库数",
"CREATION_TIME": "创建时间",
"DESCRIPTION": "描述",
"PUBLIC": "公开",
"PRIVATE": "私有",
"MAKE": "设为",
@ -395,8 +395,8 @@
"TITLE": "统计",
"PRO_ITEM": "项目",
"REPO_ITEM": "镜像库",
"INDEX_MY": "私有",
"INDEX_PUB": "公开",
"INDEX_MY": "私有",
"INDEX_PUB": "公开",
"INDEX_TOTAL": "总计"
},
"SEARCH": {