Merge pull request #1791 from wknet123/dev-revised

Merge latest updates of UI.
This commit is contained in:
Steven Zou 2017-03-24 18:42:38 +08:00 committed by GitHub
commit 28a513f900
36 changed files with 273 additions and 178 deletions

View File

@ -51,7 +51,9 @@ export class SignInComponent implements AfterViewChecked, OnInit {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.appConfig = this.appConfigService.getConfig(); //Make sure the updated configuration can be loaded
this.appConfigService.load()
.then(updatedConfig => this.appConfig = updatedConfig);
this.route.queryParams this.route.queryParams
.subscribe(params => { .subscribe(params => {
this.redirectUrl = params["redirect_url"] || ""; this.redirectUrl = params["redirect_url"] || "";
@ -108,7 +110,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
//Fill the new user info into the sign in form //Fill the new user info into the sign in form
private handleUserCreation(user: User): void { private handleUserCreation(user: User): void {
if(user){ if (user) {
this.currentForm.setValue({ this.currentForm.setValue({
"login_username": user.username, "login_username": user.username,
"login_password": user.password "login_password": user.password

View File

@ -1,5 +1,5 @@
<form class="search"> <form class="search">
<label for="search_input"> <label for="search_input">
<input #globalSearchBox id="search_input" type="text" (keyup)="search(globalSearchBox.value)" placeholder='{{"GLOBAL_SEARCH.PLACEHOLDER" | translate}}'> <input #globalSearchBox name="globalSearchBox" [(ngModel)]="searchTerm" id="search_input" type="text" (keyup)="search(globalSearchBox.value)" placeholder='{{"GLOBAL_SEARCH.PLACEHOLDER" | translate}}'>
</label> </label>
</form> </form>

View File

@ -21,10 +21,11 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
//Keep subscription for future use //Keep subscription for future use
private searchSub: Subscription; private searchSub: Subscription;
private stateSub: Subscription; private closeSub: Subscription;
//To indicate if the result panel is opened //To indicate if the result panel is opened
private isResPanelOpened: boolean = false; private isResPanelOpened: boolean = false;
private searchTerm: string = "";
constructor( constructor(
private searchTrigger: SearchTriggerService, private searchTrigger: SearchTriggerService,
@ -38,6 +39,9 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
.subscribe(term => { .subscribe(term => {
this.searchTrigger.triggerSearch(term); this.searchTrigger.triggerSearch(term);
}); });
this.closeSub = this.searchTrigger.searchClearChan$.subscribe(clear => {
this.searchTerm = "";
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@ -7,8 +7,8 @@
<div class="spinner spinner-lg search-spinner" [hidden]="done">{{'SEARCH.IN_PROGRESS' | translate}}</div> <div class="spinner spinner-lg search-spinner" [hidden]="done">{{'SEARCH.IN_PROGRESS' | translate}}</div>
<div id="results"> <div id="results">
<h2>{{'PROJECT.PROJECTS' | translate}}</h2> <h2>{{'PROJECT.PROJECTS' | translate}}</h2>
<list-project [projects]="searchResults.project" [mode]="listMode"></list-project> <list-project-ro [projects]="searchResults.project"></list-project-ro>
<h2>{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</h2> <h2>{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</h2>
<list-repository [repositories]="searchResults.repository" [mode]="listMode"></list-repository> <list-repository-ro [repositories]="searchResults.repository"></list-repository-ro>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, Output, EventEmitter } from '@angular/core'; import { Component, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
import { GlobalSearchService } from './global-search.service'; import { GlobalSearchService } from './global-search.service';
import { SearchResults } from './search-results'; import { SearchResults } from './search-results';
@ -8,6 +8,8 @@ import { MessageService } from '../../global-message/message.service';
import { SearchTriggerService } from './search-trigger.service'; import { SearchTriggerService } from './search-trigger.service';
import { Subscription } from 'rxjs/Subscription';
@Component({ @Component({
selector: "search-result", selector: "search-result",
templateUrl: "search-result.component.html", templateUrl: "search-result.component.html",
@ -16,7 +18,7 @@ import { SearchTriggerService } from './search-trigger.service';
providers: [GlobalSearchService] providers: [GlobalSearchService]
}) })
export class SearchResultComponent { export class SearchResultComponent implements OnInit, OnDestroy {
private searchResults: SearchResults = new SearchResults(); private searchResults: SearchResults = new SearchResults();
private originalCopy: SearchResults; private originalCopy: SearchResults;
@ -30,11 +32,34 @@ export class SearchResultComponent {
//Whether or not mouse point is onto the close indicator //Whether or not mouse point is onto the close indicator
private mouseOn: boolean = false; private mouseOn: boolean = false;
//Watch message channel
private searchSub: Subscription;
private closeSearchSub: Subscription;
constructor( constructor(
private search: GlobalSearchService, private search: GlobalSearchService,
private msgService: MessageService, private msgService: MessageService,
private searchTrigger: SearchTriggerService) { } private searchTrigger: SearchTriggerService) { }
ngOnInit() {
this.searchSub = this.searchTrigger.searchTriggerChan$.subscribe(term => {
this.doSearch(term);
});
this.closeSearchSub = this.searchTrigger.searchCloseChan$.subscribe(close => {
this.close();
});
}
ngOnDestroy() {
if (this.searchSub) {
this.searchSub.unsubscribe();
}
if (this.closeSearchSub) {
this.closeSearchSub.unsubscribe();
}
}
private clone(src: SearchResults): SearchResults { private clone(src: SearchResults): SearchResults {
let res: SearchResults = new SearchResults(); let res: SearchResults = new SearchResults();
@ -64,27 +89,23 @@ export class SearchResultComponent {
return this.mouseOn; return this.mouseOn;
} }
//Handle mouse event of close indicator
mouseAction(over: boolean): void {
this.mouseOn = over;
}
//Show the results //Show the results
show(): void { show(): void {
this.stateIndicator = true; this.stateIndicator = true;
this.searchTrigger.searchInputStat(true);
} }
//Close the result page //Close the result page
close(): void { close(): void {
//Tell shell close
this.searchTrigger.closeSearch(true);
this.searchTrigger.searchInputStat(false);
this.stateIndicator = false; this.stateIndicator = false;
this.searchTrigger.clear(true);
} }
//Call search service to complete the search request //Call search service to complete the search request
doSearch(term: string): void { doSearch(term: string): void {
//Only search none empty term
if (!term || term.trim() === "") {
return;
}
//Do nothing if search is ongoing //Do nothing if search is ongoing
if (this.onGoing) { if (this.onGoing) {
return; return;

View File

@ -7,11 +7,11 @@ export class SearchTriggerService {
private searchTriggerSource = new Subject<string>(); private searchTriggerSource = new Subject<string>();
private searchCloseSource = new Subject<boolean>(); private searchCloseSource = new Subject<boolean>();
private searchInputSource = new Subject<boolean>(); private searchClearSource = new Subject<boolean>();
searchTriggerChan$ = this.searchTriggerSource.asObservable(); searchTriggerChan$ = this.searchTriggerSource.asObservable();
searchCloseChan$ = this.searchCloseSource.asObservable(); searchCloseChan$ = this.searchCloseSource.asObservable();
searchInputChan$ = this.searchInputSource.asObservable(); searchClearChan$ = this.searchClearSource.asObservable();
triggerSearch(event: string) { triggerSearch(event: string) {
this.searchTriggerSource.next(event); this.searchTriggerSource.next(event);
@ -23,9 +23,9 @@ export class SearchTriggerService {
this.searchCloseSource.next(event); this.searchCloseSource.next(event);
} }
//Notify the state change of search box in home start page //Clear search term
searchInputStat(event: boolean) { clear(event): void {
this.searchInputSource.next(event); this.searchClearSource.next(event);
} }
} }

View File

@ -8,7 +8,7 @@
<search-result></search-result> <search-result></search-result>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
<nav class="sidenav" *ngIf="isUserExisting" [class.side-nav-override]="showSearch" (click)='watchClickEvt()'> <nav class="sidenav" *ngIf="isUserExisting">
<section class="sidenav-content"> <section class="sidenav-content">
<a routerLink="/harbor/projects" routerLinkActive="active" class="nav-link">{{'SIDE_NAV.PROJECTS' | translate}}</a> <a routerLink="/harbor/projects" routerLinkActive="active" class="nav-link">{{'SIDE_NAV.PROJECTS' | translate}}</a>
<a routerLink="/harbor/logs" routerLinkActive="active" class="nav-link" style="margin-top: 4px;">{{'SIDE_NAV.LOGS' | translate}}</a> <a routerLink="/harbor/logs" routerLinkActive="active" class="nav-link" style="margin-top: 4px;">{{'SIDE_NAV.LOGS' | translate}}</a>

View File

@ -5,13 +5,11 @@ import { ModalEvent } from '../modal-event';
import { modalEvents } from '../modal-events.const'; import { modalEvents } from '../modal-events.const';
import { AccountSettingsModalComponent } from '../../account/account-settings/account-settings-modal.component'; import { AccountSettingsModalComponent } from '../../account/account-settings/account-settings-modal.component';
import { SearchResultComponent } from '../global-search/search-result.component';
import { PasswordSettingComponent } from '../../account/password/password-setting.component'; import { PasswordSettingComponent } from '../../account/password/password-setting.component';
import { NavigatorComponent } from '../navigator/navigator.component'; import { NavigatorComponent } from '../navigator/navigator.component';
import { SessionService } from '../../shared/session.service'; import { SessionService } from '../../shared/session.service';
import { AboutDialogComponent } from '../../shared/about-dialog/about-dialog.component'; import { AboutDialogComponent } from '../../shared/about-dialog/about-dialog.component';
import { StartPageComponent } from '../start-page/start.component';
import { SearchTriggerService } from '../global-search/search-trigger.service'; import { SearchTriggerService } from '../global-search/search-trigger.service';
@ -30,9 +28,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
@ViewChild(AccountSettingsModalComponent) @ViewChild(AccountSettingsModalComponent)
private accountSettingsModal: AccountSettingsModalComponent; private accountSettingsModal: AccountSettingsModalComponent;
@ViewChild(SearchResultComponent)
private searchResultComponet: SearchResultComponent;
@ViewChild(PasswordSettingComponent) @ViewChild(PasswordSettingComponent)
private pwdSetting: PasswordSettingComponent; private pwdSetting: PasswordSettingComponent;
@ -42,9 +37,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
@ViewChild(AboutDialogComponent) @ViewChild(AboutDialogComponent)
private aboutDialog: AboutDialogComponent; private aboutDialog: AboutDialogComponent;
@ViewChild(StartPageComponent)
private searchSatrt: StartPageComponent;
//To indicator whwther or not the search results page is displayed //To indicator whwther or not the search results page is displayed
//We need to use this property to do some overriding work //We need to use this property to do some overriding work
private isSearchResultsOpened: boolean = false; private isSearchResultsOpened: boolean = false;
@ -60,15 +52,13 @@ export class HarborShellComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.searchSub = this.searchTrigger.searchTriggerChan$.subscribe(searchEvt => { this.searchSub = this.searchTrigger.searchTriggerChan$.subscribe(searchEvt => {
this.doSearch(searchEvt); if(searchEvt && searchEvt.trim() != ""){
this.isSearchResultsOpened = true;
}
}); });
this.searchCloseSub = this.searchTrigger.searchCloseChan$.subscribe(close => { this.searchCloseSub = this.searchTrigger.searchCloseChan$.subscribe(close => {
if (close) { this.isSearchResultsOpened = false;
this.searchClose();
}else{
this.watchClickEvt();//reuse
}
}); });
} }
@ -116,30 +106,4 @@ export class HarborShellComponent implements OnInit, OnDestroy {
break; break;
} }
} }
//Handle the global search event and then let the result page to trigger api
doSearch(event: string): void {
if (event === "") {
//Do nothing
return;
}
//Once this method is called
//the search results page must be opened
this.isSearchResultsOpened = true;
//Call the child component to do the real work
this.searchResultComponet.doSearch(event);
}
//Search results page closed
//remove the related ovevriding things
searchClose(): void {
this.isSearchResultsOpened = false;
}
//Close serch result panel if existing
watchClickEvt(): void {
this.searchResultComponet.close();
this.isSearchResultsOpened = false;
}
} }

View File

@ -105,7 +105,7 @@ export class NavigatorComponent implements OnInit {
this.msgService.announceMessage(error.status | 500, errorHandler(error), AlertType.WARNING); this.msgService.announceMessage(error.status | 500, errorHandler(error), AlertType.WARNING);
}); });
//Confirm search result panel is close //Confirm search result panel is close
this.searchTrigger.closeSearch(false); this.searchTrigger.closeSearch(true);
} }
//Switch languages //Switch languages
@ -132,10 +132,10 @@ export class NavigatorComponent implements OnInit {
} }
//Confirm search result panel is close //Confirm search result panel is close
this.searchTrigger.closeSearch(false); this.searchTrigger.closeSearch(true);
} }
registryAction(): void { registryAction(): void {
this.searchTrigger.closeSearch(false); this.searchTrigger.closeSearch(true);
} }
} }

View File

@ -125,7 +125,7 @@
</div> </div>
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right"> <a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon> <clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.AUTH_MODE' | translate}}</span> <span class="tooltip-content">{{'CONFIG.TOOLTIP.PRO_CREATION_RESTRICTION' | translate}}</span>
</a> </a>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -243,12 +243,20 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
*/ */
public testMailServer(): void { public testMailServer(): void {
let mailSettings = {}; let mailSettings = {};
let allChanges = this.getChanges(); for (let prop in this.allConfig) {
for (let prop in allChanges) {
if (prop.startsWith("email_")) { if (prop.startsWith("email_")) {
mailSettings[prop] = allChanges[prop]; mailSettings[prop] = this.allConfig[prop].value;
} }
} }
//Confirm port is number
mailSettings["email_port"] = +mailSettings["email_port"];
let allChanges = this.getChanges();
let password = allChanges["email_password"]
if (password) {
mailSettings["email_password"] = password;
} else {
delete mailSettings["email_password"];
}
this.testingOnGoing = true; this.testingOnGoing = true;
this.configService.testMailServer(mailSettings) this.configService.testMailServer(mailSettings)
@ -264,14 +272,21 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
public testLDAPServer(): void { public testLDAPServer(): void {
let ldapSettings = {}; let ldapSettings = {};
for (let prop in this.allConfig) {
if (prop.startsWith("ldap_")) {
ldapSettings[prop] = this.allConfig[prop].value;
}
}
let allChanges = this.getChanges(); let allChanges = this.getChanges();
for (let prop in allChanges) { for(let prop in allChanges){
if (prop.startsWith("ldap_")) { if (prop.startsWith("ldap_")) {
ldapSettings[prop] = allChanges[prop]; ldapSettings[prop] = allChanges[prop];
} }
} }
console.info(ldapSettings); console.info(ldapSettings);
this.testingOnGoing = true; this.testingOnGoing = true;
this.configService.testLDAPServer(ldapSettings) this.configService.testLDAPServer(ldapSettings)
.then(respone => { .then(respone => {
@ -296,7 +311,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
this.confirmService.openComfirmDialog(msg); this.confirmService.openComfirmDialog(msg);
} }
private confirmUnsavedTabChanges(changes: any, tabId: string){ private confirmUnsavedTabChanges(changes: any, tabId: string) {
let msg = new ConfirmationMessage( let msg = new ConfirmationMessage(
"CONFIG.CONFIRM_TITLE", "CONFIG.CONFIRM_TITLE",
"CONFIG.CONFIRM_SUMMARY", "CONFIG.CONFIRM_SUMMARY",

View File

@ -35,8 +35,8 @@ export class RecentLogComponent implements OnInit {
} }
private handleOnchange($event: any) { private handleOnchange($event: any) {
if (event && event.target && event.srcElement["value"]) { if ($event && $event.target && $event.target["value"]) {
this.lines = event.srcElement["value"]; this.lines = $event.target["value"];
if (this.lines < 10) { if (this.lines < 10) {
this.lines = 10; this.lines = 10;
} }

View File

@ -5,7 +5,7 @@
<clr-dg-column>{{'PROJECT.REPO_COUNT'| 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.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p"> <clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
<clr-dg-action-overflow [hidden]="!listFullMode || p.current_user_role_id !== 1"> <clr-dg-action-overflow [hidden]="p.current_user_role_id !== 1">
<button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button> <button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
<button class="action-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </button> <button class="action-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </button>
<button class="action-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</button> <button class="action-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</button>

View File

@ -5,15 +5,14 @@ import { ProjectService } from '../project.service';
import { SessionService } from '../../shared/session.service'; import { SessionService } from '../../shared/session.service';
import { SearchTriggerService } from '../../base/global-search/search-trigger.service'; import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
import { ListMode, ProjectTypes, RoleInfo } from '../../shared/shared.const'; import { ProjectTypes, RoleInfo } from '../../shared/shared.const';
import { State } from 'clarity-angular'; import { State } from 'clarity-angular';
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: 'list-project', selector: 'list-project',
templateUrl: 'list-project.component.html', templateUrl: 'list-project.component.html'
styleUrls: ['./list-project.component.css']
}) })
export class ListProjectComponent implements OnInit { export class ListProjectComponent implements OnInit {
@ -31,8 +30,6 @@ export class ListProjectComponent implements OnInit {
@Output() toggle = new EventEmitter<Project>(); @Output() toggle = new EventEmitter<Project>();
@Output() delete = new EventEmitter<Project>(); @Output() delete = new EventEmitter<Project>();
@Input() mode: string = ListMode.FULL;
roleInfo = RoleInfo; roleInfo = RoleInfo;
constructor( constructor(
@ -43,12 +40,8 @@ export class ListProjectComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
} }
public get listFullMode(): boolean {
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
}
get showRoleInfo(): boolean { get showRoleInfo(): boolean {
return this.listFullMode && this.filteredType === ProjectTypes[0]; return this.filteredType === ProjectTypes[0];
} }
public get isSystemAdmin(): boolean { public get isSystemAdmin(): boolean {
@ -57,19 +50,10 @@ export class ListProjectComponent implements OnInit {
} }
goToLink(proId: number): void { goToLink(proId: number): void {
this.searchTrigger.closeSearch(false); this.searchTrigger.closeSearch(true);
let linkUrl = ['harbor', 'projects', proId, 'repository']; let linkUrl = ['harbor', 'projects', proId, 'repository'];
if (!this.session.getCurrentUser()) { this.router.navigate(linkUrl);
let navigatorExtra: NavigationExtras = {
queryParams: { "guest": true }
};
this.router.navigate(linkUrl, navigatorExtra);
} else {
this.router.navigate(linkUrl);
}
} }
refresh(state: State) { refresh(state: State) {

View File

@ -1,5 +1,7 @@
<a style="display: block;" [routerLink]="['/harbor', 'projects']">&lt; {{'PROJECT_DETAIL.PROJECTS' | translate}}</a> <a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects']">&lt; {{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
<h1 class="sub-header-title">{{currentProject.name}}</h1> <a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">&lt; {{'SEARCH.BACK' | translate}}</a>
<h1 class="sub-header-title">{{currentProject.name}} <span class="badge badge-light-blue" *ngIf="isMember">{{roleName | translate}}</span> <span class="badge badge-purple" *ngIf="isSystemAdmin">{{ 'MEMBER.SYS_ADMIN' | translate}}</span></h1>
<nav class="subnav sub-nav-bg-color"> <nav class="subnav sub-nav-bg-color">
<ul class="nav"> <ul class="nav">
<li class="nav-item"> <li class="nav-item">

View File

@ -6,6 +6,8 @@ import { Project } from '../project';
import { SessionService } from '../../shared/session.service'; import { SessionService } from '../../shared/session.service';
import { ProjectService } from '../../project/project.service'; import { ProjectService } from '../../project/project.service';
import { RoleMapping } from '../../shared/shared.const';
@Component({ @Component({
selector: 'project-detail', selector: 'project-detail',
templateUrl: "project-detail.component.html", templateUrl: "project-detail.component.html",
@ -13,8 +15,11 @@ import { ProjectService } from '../../project/project.service';
}) })
export class ProjectDetailComponent { export class ProjectDetailComponent {
hasSignedIn: boolean;
currentProject: Project; currentProject: Project;
isMember: boolean; isMember: boolean;
roleName: string;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -22,9 +27,11 @@ export class ProjectDetailComponent {
private sessionService: SessionService, private sessionService: SessionService,
private projectService: ProjectService) { private projectService: ProjectService) {
this.hasSignedIn = this.sessionService.getCurrentUser() !== null;
this.route.data.subscribe(data=>{ this.route.data.subscribe(data=>{
this.currentProject = <Project>data['projectResolver']; this.currentProject = <Project>data['projectResolver'];
this.isMember = this.currentProject.is_member; this.isMember = this.currentProject.is_member;
this.roleName = RoleMapping[this.currentProject.role_name];
}); });
} }

View File

@ -23,13 +23,21 @@ export class ProjectRoutingResolver implements Resolve<Project>{
.then((project: Project)=> { .then((project: Project)=> {
if(project) { if(project) {
let currentUser = this.sessionService.getCurrentUser(); let currentUser = this.sessionService.getCurrentUser();
let projectMembers = this.sessionService.getProjectMembers(); if(currentUser) {
if(currentUser && projectMembers) { if(currentUser.has_admin_role === 1) {
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id); project.has_project_admin_role = true;
if(currentMember) { project.role_name = 'sysAdmin';
project.is_member = true; } else {
project.has_project_admin_role = (currentMember.role_name === 'projectAdmin') || currentUser.has_admin_role === 1; let projectMembers = this.sessionService.getProjectMembers();
} if(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');
project.role_name = currentMember.role_name;
}
}
}
} }
return project; return project;
} else { } else {

View File

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

View File

@ -31,4 +31,5 @@ export class Project {
repo_count: number; repo_count: number;
has_project_admin_role: boolean; has_project_admin_role: boolean;
is_member: boolean; is_member: boolean;
role_name: string;
} }

View File

@ -174,6 +174,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'}); this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
} else { } else {
this.createEditDestinationOpened = false; this.createEditDestinationOpened = false;
this.targetForm.reset();
} }
} }

View File

@ -1,19 +1,19 @@
<clr-datagrid (clrDgRefresh)="refresh($event)"> <clr-datagrid (clrDgRefresh)="refresh($event)">
<clr-dg-column>{{'REPOSITORY.NAME' | translate}}</clr-dg-column> <clr-dg-column>{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column> <clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column> <clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]='r'> <clr-dg-row *ngFor="let r of repositories" [clrDgItem]='r'>
<clr-dg-action-overflow *ngIf="listFullMode && hasProjectAdminRole"> <clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
<button class="action-item">{{'REPOSITORY.COPY_ID' | translate}}</button> <button class="action-item">{{'REPOSITORY.COPY_ID' | translate}}</button>
<button class="action-item">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button> <button class="action-item">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button>
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button> <button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-overflow> </clr-dg-action-overflow>
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name || r.repository_name}}</a></clr-dg-cell> <clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name || r.repository_name}}</a></clr-dg-cell>
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell> <clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell> <clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer> <clr-dg-footer>
{{totalRecordCount || (repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}} {{totalRecordCount || (repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination> <clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
</clr-dg-footer> </clr-dg-footer>
</clr-datagrid> </clr-datagrid>

View File

@ -1,13 +1,9 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { Router, NavigationExtras } from '@angular/router'; import { Router } from '@angular/router';
import { Repository } from '../repository'; import { Repository } from '../repository';
import { State } from 'clarity-angular'; import { State } from 'clarity-angular';
import { SearchTriggerService } from '../../base/global-search/search-trigger.service'; import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
import { SessionService } from '../../shared/session.service';
import { ListMode } from '../../shared/shared.const';
import { SessionUser } from '../../shared/session-user';
@Component({ @Component({
selector: 'list-repository', selector: 'list-repository',
@ -17,7 +13,7 @@ export class ListRepositoryComponent implements OnInit {
@Input() projectId: number; @Input() projectId: number;
@Input() repositories: Repository[]; @Input() repositories: Repository[];
@Output() delete = new EventEmitter<string>(); @Output() delete = new EventEmitter<string>();
@ -25,17 +21,15 @@ export class ListRepositoryComponent implements OnInit {
@Input() totalRecordCount: number; @Input() totalRecordCount: number;
@Output() paginate = new EventEmitter<State>(); @Output() paginate = new EventEmitter<State>();
@Input() mode: string = ListMode.FULL;
@Input() hasProjectAdminRole: boolean; @Input() hasProjectAdminRole: boolean;
pageOffset: number = 1; pageOffset: number = 1;
constructor( constructor(
private router: Router, private router: Router,
private searchTrigger: SearchTriggerService, private searchTrigger: SearchTriggerService) { }
private session: SessionService) { }
ngOnInit() {} ngOnInit() { }
deleteRepo(repoName: string) { deleteRepo(repoName: string) {
this.delete.emit(repoName); this.delete.emit(repoName);
@ -47,22 +41,11 @@ export class ListRepositoryComponent implements OnInit {
} }
} }
public get listFullMode(): boolean {
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
}
public gotoLink(projectId: number, repoName: string): void { public gotoLink(projectId: number, repoName: string): void {
this.searchTrigger.closeSearch(false); this.searchTrigger.closeSearch(true);
let linkUrl = ['harbor', 'tags', projectId, repoName]; let linkUrl = ['harbor', 'tags', projectId, repoName];
if (!this.session.getCurrentUser()) { this.router.navigate(linkUrl);
let navigatorExtra: NavigationExtras = {
queryParams: { "guest": true }
};
this.router.navigate(linkUrl, navigatorExtra);
} else {
this.router.navigate(linkUrl);
}
} }
} }

View File

@ -16,11 +16,6 @@ import { State } from 'clarity-angular';
import { Project } from '../project/project'; import { Project } from '../project/project';
const repositoryTypes = [
{ key: '0', description: 'REPOSITORY.MY_REPOSITORY' },
{ key: '1', description: 'REPOSITORY.PUBLIC_REPOSITORY' }
];
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: 'repository', selector: 'repository',
@ -31,8 +26,7 @@ export class RepositoryComponent implements OnInit {
changedRepositories: Repository[]; changedRepositories: Repository[];
projectId: number; projectId: number;
repositoryTypes = repositoryTypes;
currentRepositoryType: {};
lastFilteredRepoName: string; lastFilteredRepoName: string;
page: number = 1; page: number = 1;
@ -80,7 +74,6 @@ export class RepositoryComponent implements OnInit {
if(resolverData) { if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role; this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
} }
this.currentRepositoryType = this.repositoryTypes[0];
this.lastFilteredRepoName = ''; this.lastFilteredRepoName = '';
this.retrieve(); this.retrieve();
} }
@ -108,10 +101,6 @@ export class RepositoryComponent implements OnInit {
); );
} }
doFilterRepositoryByType(type: string) {
this.currentRepositoryType = this.repositoryTypes.find(r => r.key == type);
}
doSearchRepoNames(repoName: string) { doSearchRepoNames(repoName: string) {
this.lastFilteredRepoName = repoName; this.lastFilteredRepoName = repoName;
this.retrieve(); this.retrieve();

View File

@ -1,4 +1,6 @@
<a [routerLink]="['/harbor', 'projects', projectId, 'repository']">&lt; {{'REPOSITORY.REPOSITORIES' | translate}}</a> <a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects', projectId, 'repository']">&lt; {{'REPOSITORY.REPOSITORIES' | translate}}</a>
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">&lt; {{'SEARCH.BACK' | translate}}</a>
<h2 class="sub-header-title">{{repoName}} <span class="badge">{{tags ? tags.length : 0}}</span></h2> <h2 class="sub-header-title">{{repoName}} <span class="badge">{{tags ? tags.length : 0}}</span></h2>
<clr-datagrid> <clr-datagrid>
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column> <clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>

View File

@ -36,6 +36,8 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
registryUrl: string; registryUrl: string;
withNotary: boolean; withNotary: boolean;
hasSignedIn: boolean;
private subscription: Subscription; private subscription: Subscription;
constructor( constructor(
@ -74,8 +76,8 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
} }
ngOnInit() { ngOnInit() {
this.hasSignedIn = (this.session.getCurrentUser() !== null);
let resolverData = this.route.snapshot.data; let resolverData = this.route.snapshot.data;
console.log(JSON.stringify(resolverData));
if(resolverData) { if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role; this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
} }

View File

@ -3,6 +3,6 @@
<h3 style="margin-top: 0px;">{{'REPOSITORY.POP_REPOS' | translate}}</h3> <h3 style="margin-top: 0px;">{{'REPOSITORY.POP_REPOS' | translate}}</h3>
</div> </div>
<div> <div>
<list-repository [repositories]="topRepos" [mode]="listMode"></list-repository> <list-repository-ro [repositories]="topRepos"></list-repository-ro>
</div> </div>
</div> </div>

View File

@ -29,8 +29,8 @@ export class FilterComponent implements OnInit{
ngOnInit(): void { ngOnInit(): void {
this.filterTerms this.filterTerms
.debounceTime(300) .debounceTime(500)
.distinctUntilChanged() //.distinctUntilChanged()
.subscribe(terms => { .subscribe(terms => {
this.filterEvt.emit(terms); this.filterEvt.emit(terms);
}); });

View File

@ -0,0 +1,16 @@
<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>{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
<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>{{p.repo_count}}</clr-dg-cell>
<clr-dg-cell>{{p.creation_time}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -0,0 +1,37 @@
import { Component, EventEmitter, Output, Input } from '@angular/core';
import { Router } from '@angular/router';
import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
import { Project } from '../../project/project';
import { State } from 'clarity-angular';
@Component({
moduleId: module.id,
selector: 'list-project-ro',
templateUrl: 'list-project-ro.component.html'
})
export class ListProjectROComponent {
@Input() projects: Project[];
@Input() totalPage: number;
@Input() totalRecordCount: number;
pageOffset: number = 1;
@Output() paginate = new EventEmitter<State>();
constructor(
private searchTrigger: SearchTriggerService,
private router: Router) { }
goToLink(proId: number): void {
this.searchTrigger.closeSearch(true);
let linkUrl = ['harbor', 'projects', proId, 'repository'];
this.router.navigate(linkUrl);
}
refresh(state: State) {
this.paginate.emit(state);
}
}

View File

@ -0,0 +1,14 @@
<clr-datagrid (clrDgRefresh)="refresh($event)">
<clr-dg-column>{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]='r'>
<clr-dg-cell><a href="javascript:void(0)" (click)="gotoLink(projectId || r.project_id, r.name || r.repository_name)">{{r.name || r.repository_name}}</a></clr-dg-cell>
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{totalRecordCount || (repositories ? repositories.length : 0)}} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -0,0 +1,40 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';
import { Repository } from '../../repository/repository';
import { State } from 'clarity-angular';
import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
@Component({
selector: 'list-repository-ro',
templateUrl: 'list-repository-ro.component.html'
})
export class ListRepositoryROComponent {
@Input() projectId: number;
@Input() repositories: Repository[];
@Input() totalPage: number;
@Input() totalRecordCount: number;
@Output() paginate = new EventEmitter<State>();
pageOffset: number = 1;
constructor(
private router: Router,
private searchTrigger: SearchTriggerService
) { }
refresh(state: State) {
if (this.repositories) {
this.paginate.emit(state);
}
}
public gotoLink(projectId: number, repoName: string): void {
this.searchTrigger.closeSearch(true);
let linkUrl = ['harbor', 'tags', projectId, repoName];
this.router.navigate(linkUrl);
}
}

View File

@ -1,7 +1,7 @@
import { Directive } from '@angular/core'; import { Directive } from '@angular/core';
import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, Validators } from '@angular/forms'; import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, Validators } from '@angular/forms';
export const portNumbers = /[\d]+/; export const portNumbers = /^[\d]{1,5}$/;
export function portValidator(): ValidatorFn { export function portValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl): { [key: string]: any } => {

View File

@ -19,15 +19,10 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
private appConfigService: AppConfigService) { } private appConfigService: AppConfigService) { }
private isGuest(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { private isGuest(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let queryParams = route.queryParams; const proRegExp = /\/harbor\/projects\/[\d]+\/.+/i;
if (queryParams) { const libRegExp = /\/harbor\/tags\/[\d]+\/.+/i;
if (queryParams["guest"]) { if (proRegExp.test(state.url) || libRegExp.test(state.url)) {
let proRegExp = /\/harbor\/projects\/[\d]+\/.+/i; return true;
const libRegExp = /\/harbor\/tags\/[\d]+\/.+/i;
if (proRegExp.test(state.url)|| libRegExp.test(state.url)) {
return true;
}
}
} }
return false; return false;
@ -42,8 +37,8 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
this.appConfigService.saveAdmiralEndpoint(queryParams[AdmiralQueryParamKey]); this.appConfigService.saveAdmiralEndpoint(queryParams[AdmiralQueryParamKey]);
//Remove the query parameter key pair and redirect //Remove the query parameter key pair and redirect
let keyRemovedUrl = maintainUrlQueryParmas(state.url, AdmiralQueryParamKey, undefined); let keyRemovedUrl = maintainUrlQueryParmas(state.url, AdmiralQueryParamKey, undefined);
if(!/[?]{1}.+/i.test(keyRemovedUrl)){ if (!/[?]{1}.+/i.test(keyRemovedUrl)) {
keyRemovedUrl = keyRemovedUrl.replace('?',''); keyRemovedUrl = keyRemovedUrl.replace('?', '');
} }
this.router.navigateByUrl(keyRemovedUrl); this.router.navigateByUrl(keyRemovedUrl);

View File

@ -56,3 +56,4 @@ export const enum ConfirmationState {
export const ProjectTypes = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' }; export const ProjectTypes = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
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' };
export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' };

View File

@ -34,6 +34,9 @@ import { SignInGuard } from './route/sign-in-guard-activate.service';
import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.service'; import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.service';
import { MemberGuard } from './route/member-guard-activate.service'; import { MemberGuard } from './route/member-guard-activate.service';
import { ListProjectROComponent } from './list-project-ro/list-project-ro.component';
import { ListRepositoryROComponent } from './list-repository-ro/list-repository-ro.component';
@NgModule({ @NgModule({
imports: [ imports: [
CoreModule, CoreModule,
@ -53,7 +56,9 @@ import { MemberGuard } from './route/member-guard-activate.service';
PageNotFoundComponent, PageNotFoundComponent,
AboutDialogComponent, AboutDialogComponent,
StatisticsComponent, StatisticsComponent,
StatisticsPanelComponent StatisticsPanelComponent,
ListProjectROComponent,
ListRepositoryROComponent
], ],
exports: [ exports: [
CoreModule, CoreModule,
@ -70,7 +75,9 @@ import { MemberGuard } from './route/member-guard-activate.service';
PageNotFoundComponent, PageNotFoundComponent,
AboutDialogComponent, AboutDialogComponent,
StatisticsComponent, StatisticsComponent,
StatisticsPanelComponent StatisticsPanelComponent,
ListProjectROComponent,
ListRepositoryROComponent
], ],
providers: [ providers: [
SessionService, SessionService,