Merge pull request #1386 from wknet123/harbor-clarity

Add project components to UI.
This commit is contained in:
Steven Zou 2017-02-16 17:46:47 +08:00 committed by GitHub
commit e7b8a5492c
15 changed files with 229 additions and 83 deletions

View File

@ -1,16 +1,21 @@
<clr-modal [(clrModalOpen)]="create_project_opened">
<clr-modal [(clrModalOpen)]="createProjectOpened">
<h3 class="modal-title">New Project</h3>
<div class="modal-body">
<form>
<section class="form-block">
<div class="form-group">
<label for="create_project_username" class="col-md-4">Project Name</label>
<input type="text" class="col-md-8" id="create_project_username" size="20">
<label for="create_project_name" class="col-md-4">Project Name</label>
<label for="create_project_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="create_project_name" [(ngModel)]="project.name" name="name" size="20" (keyup)="hasError=false;">
<span class="tooltip-content">
{{errorMessage}}
</span>
</label>
</div>
<div class="form-group">
<label class="col-md-4">Public</label>
<div class="checkbox-inline">
<input type="checkbox" id="create_project_public">
<input type="checkbox" id="create_project_public" [(ngModel)]="project.public" name="public">
<label for="create_project_public"></label>
</div>
</div>
@ -18,8 +23,8 @@
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="create_project_opened = false">Cancel</button>
<button type="button" class="btn btn-primary" (click)="create_project_opened = false">Ok</button>
<button type="button" class="btn btn-outline" (click)="createProjectOpened = false">Cancel</button>
<button type="button" class="btn btn-primary" (click)="onSubmit()">Ok</button>
</div>
</clr-modal>
<button class="btn btn-sm" (click)="create_project_opened = true">New Project</button>
<button class="btn btn-sm" (click)="newProject()">New Project</button>

View File

@ -1,9 +1,55 @@
import { Component } from '@angular/core';
import { Component, EventEmitter, Output } from '@angular/core';
import { Response } from '@angular/http';
import { Project } from '../project';
import { ProjectService } from '../project.service';
@Component({
selector: 'create-project',
templateUrl: 'create-project.component.html'
templateUrl: 'create-project.component.html',
styleUrls: [ 'create-project.css' ]
})
export class CreateProjectComponent {
project: Project = new Project();
createProjectOpened: boolean;
errorMessage: string;
hasError: boolean;
@Output() create = new EventEmitter<boolean>();
constructor(private projectService: ProjectService) {}
onSubmit() {
this.hasError = false;
this.projectService
.createProject(this.project.name, this.project.public)
.subscribe(
status=>{
this.create.emit(true);
this.createProjectOpened = false;
},
error=>{
this.hasError = true;
if (error instanceof Response) {
switch(error.status) {
case 409:
this.errorMessage = 'Project name already exists.'; break;
case 400:
this.errorMessage = 'Project name is illegal.'; break;
default:
this.errorMessage = 'Unknown error for project name.';
}
}
});
}
newProject() {
this.hasError = false;
this.project = new Project();
this.createProjectOpened = true;
}
}

View File

@ -1,10 +1,9 @@
<clr-dropdown [clrMenuPosition]="'bottom-left'">
<button class="btn btn-sm btn-outline-primary" clrDropdownToggle>
My Projects
{{currentType.value}}
<clr-icon shape="caret down"></clr-icon>
</button>
<div class="dropdown-menu">
<a href="javascript:void(0)" clrDropdownItem>My Projects</a>
<a href="javascript:void(0)" clrDropdownItem>Public Projects</a>
<a href="javascript:void(0)" clrDropdownItem *ngFor="let p of types" (click)="doFilter(p.key)">{{p.value}}</a>
</div>
</clr-dropdown>

View File

@ -1,7 +1,23 @@
import { Component } from '@angular/core';
import { Component, Output, EventEmitter } from '@angular/core';
export const projectTypes = [
{ 'key' : 0, 'value': 'My Projects' },
{ 'key' : 1, 'value': 'Public Projects'}
];
@Component({
selector: 'filter-project',
templateUrl: 'filter-project.component.html'
})
export class FilterProjectComponent {}
export class FilterProjectComponent {
@Output() filter = new EventEmitter<number>();
types = projectTypes;
currentType = projectTypes[0];
doFilter(type: number) {
console.log('Filtered projects by:' + type);
this.currentType = projectTypes.find(item=>item.key === type);
this.filter.emit(type);
}
}

View File

@ -8,12 +8,12 @@
<clr-dg-column>Description</clr-dg-column>
<clr-dg-row *ngFor="let p of projects">
<clr-dg-cell><a [routerLink]="['/harbor', 'projects', p.id, 'repository']" >{{p.name}}</a></clr-dg-cell>
<clr-dg-cell>{{p.isPublic ? 'Public': 'Private'}}</clr-dg-cell>
<clr-dg-cell>{{p.repoCount}}</clr-dg-cell>
<clr-dg-cell>{{p.creationTime}}</clr-dg-cell>
<clr-dg-cell>{{p.destination}}</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>
<clr-dg-cell>--</clr-dg-cell>
<clr-dg-cell>{{p.owner}}</clr-dg-cell>
<clr-dg-cell>{{p.description}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{projects.length}} item(s)</clr-dg-footer>
<clr-dg-footer>{{ (projects ? projects.length : 0) }} item(s)</clr-dg-footer>
</clr-datagrid>

View File

@ -1,46 +1,24 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { Project } from '../project';
import { ProjectService } from '../project.service';
@Component({
selector: 'list-project',
templateUrl: 'list-project.component.html'
})
export class ListProjectComponent implements OnInit {
projects: Project[];
export class ListProjectComponent {
ngOnInit(): void {
this.projects = [
{
id: 1,
name: 'Project01',
isPublic: true,
repoCount: 0,
creationTime: '2016-12-29 14:58 PM',
destination: '10.117.4.61',
owner: 'Administrator',
description: 'New updated by Alice.'
},
{
id: 2,
name: 'Project02',
isPublic: false,
repoCount: 2,
creationTime: '2016-12-29 15:28 PM',
destination: '10.117.4.61',
owner: 'Administrator',
description: '--'
},
{
id: 3,
name: 'Project03',
isPublic: true,
repoCount: 5,
creationTime: '2016-12-21 10:25 AM',
destination: '10.117.4.61',
owner: 'Administrator',
description: 'Deprecated'
},
];
projects: Project[];
errorMessage: string;
constructor(private projectService: ProjectService) {}
retrieve(name: string, isPublic: number): void {
this.projectService
.listProjects(name, isPublic)
.subscribe(
response => this.projects = response,
error => this.errorMessage = <any>error);
}
}

View File

@ -1,20 +1,11 @@
<h3>Projects</h3>
<div class="row flex-items-xs-between">
<div class="col-xs-4">
<create-project></create-project>
<create-project (create)="createProject($event)"></create-project>
</div>
<div class="col-xs-4">
<clr-dropdown [clrMenuPosition]="'bottom-left'">
<button class="btn btn-sm btn-outline-primary" clrDropdownToggle>
My Projects
<clr-icon shape="caret down"></clr-icon>
</button>
<div class="dropdown-menu">
<a href="javascript:void(0)" clrDropdownItem>My Projects</a>
<a href="javascript:void(0)" clrDropdownItem>Public Projects</a>
</div>
</clr-dropdown>
<search-project></search-project>
<filter-project (filter)="filterProjects($event)"></filter-project>
<search-project (search)="searchProjects($event)"></search-project>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<list-project></list-project>

View File

@ -1,14 +1,39 @@
import { Component, OnInit } from '@angular/core';
import { Project } from './project';
import { Component, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { ListProjectComponent } from './list-project/list-project.component';
@Component({
selector: 'project',
templateUrl: 'project.component.html',
styleUrls: [ 'project.css' ]
})
export class ProjectComponent implements OnInit {
ngOnInit(): void {
@ViewChild(ListProjectComponent)
listProjects: ListProjectComponent;
lastFilteredType: number = 0;
createProject(created: boolean): void {
console.log('Project has been created:' + created);
this.listProjects.retrieve('', 0);
}
filterProjects(type: number): void {
this.lastFilteredType = type;
this.listProjects.retrieve('', type);
console.log('Projects were filtered by:' + type);
}
searchProjects(projectName: string): void {
console.log('Search for project name:' + projectName);
this.listProjects.retrieve(projectName, this.lastFilteredType);
}
ngOnInit(): void {
this.listProjects.retrieve('', 0);
}
}

View File

@ -14,6 +14,7 @@ import { ProjectDetailComponent } from './project-detail/project-detail.componen
import { MemberComponent } from './member/member.component';
import { ProjectRoutingModule } from './project-routing.module';
import { ProjectService } from './project.service';
@NgModule({
imports: [
@ -32,6 +33,9 @@ import { ProjectRoutingModule } from './project-routing.module';
ProjectDetailComponent,
MemberComponent
],
exports: [ ListProjectComponent ]
exports: [ ListProjectComponent ],
providers: [ ProjectService ]
})
export class ProjectModule {}
export class ProjectModule {
}

View File

@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions } from '@angular/http';
import { Project } from './project';
import { BaseService } from '../service/base.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
@Injectable()
export class ProjectService extends BaseService {
headers = new Headers({'Content-type': 'application/json'});
options = new RequestOptions({'headers': this.headers});
constructor(private http: Http) {
super();
}
listProjects(name: string, isPublic: number): Observable<Project[]>{
return this.http
.get(`/ng/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`,
JSON.stringify({'project_name': name, 'public': (isPublic ? 1 : 0)})
, this.options)
.map(response=>response.status)
.catch(error=>Observable.throw(error));
}
}

View File

@ -1,10 +1,32 @@
/*
[
{
"project_id": 1,
"owner_id": 1,
"name": "library",
"creation_time": "2017-02-10T07:57:56Z",
"creation_time_str": "",
"deleted": 0,
"owner_name": "",
"public": 1,
"Togglable": true,
"update_time": "2017-02-10T07:57:56Z",
"current_user_role_id": 1,
"repo_count": 0
}
]
*/
export class Project {
id: number;
project_id: number;
owner_id: number;
name: string;
isPublic: boolean;
repoCount: number;
creationTime: string;
destination: string;
owner: string;
description: string;
creation_time: Date;
creation_time_str: string;
deleted: number;
owner_name: string;
public: number;
Togglable: boolean;
update_time: Date;
current_user_role_id: number;
repo_count: number;
}

View File

@ -1 +1 @@
<input type="text" placeholder="Search for projects">
<input type="text" placeholder="Search for projects" #searchProject (keyup.enter)="doSearch(searchProject.value)" >

View File

@ -1,9 +1,13 @@
import { Component } from '@angular/core';
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'search-project',
templateUrl: 'search-project.component.html'
})
export class SearchProjectComponent {
@Output() search = new EventEmitter<string>();
doSearch(projectName) {
this.search.emit(projectName);
}
}

View File

@ -0,0 +1,18 @@
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
export abstract class BaseService {
protected handleError(error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
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();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}