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 {
this.appConfig = this.appConfigService.getConfig();
//Make sure the updated configuration can be loaded
this.appConfigService.load()
.then(updatedConfig => this.appConfig = updatedConfig);
this.route.queryParams
.subscribe(params => {
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
private handleUserCreation(user: User): void {
if(user){
if (user) {
this.currentForm.setValue({
"login_username": user.username,
"login_password": user.password

View File

@ -1,5 +1,5 @@
<form class="search">
<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>
</form>

View File

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

View File

@ -7,8 +7,8 @@
<div class="spinner spinner-lg search-spinner" [hidden]="done">{{'SEARCH.IN_PROGRESS' | translate}}</div>
<div id="results">
<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>
<list-repository [repositories]="searchResults.repository" [mode]="listMode"></list-repository>
<list-repository-ro [repositories]="searchResults.repository"></list-repository-ro>
</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 { SearchResults } from './search-results';
@ -8,6 +8,8 @@ import { MessageService } from '../../global-message/message.service';
import { SearchTriggerService } from './search-trigger.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: "search-result",
templateUrl: "search-result.component.html",
@ -16,7 +18,7 @@ import { SearchTriggerService } from './search-trigger.service';
providers: [GlobalSearchService]
})
export class SearchResultComponent {
export class SearchResultComponent implements OnInit, OnDestroy {
private searchResults: SearchResults = new SearchResults();
private originalCopy: SearchResults;
@ -30,11 +32,34 @@ export class SearchResultComponent {
//Whether or not mouse point is onto the close indicator
private mouseOn: boolean = false;
//Watch message channel
private searchSub: Subscription;
private closeSearchSub: Subscription;
constructor(
private search: GlobalSearchService,
private msgService: MessageService,
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 {
let res: SearchResults = new SearchResults();
@ -64,27 +89,23 @@ export class SearchResultComponent {
return this.mouseOn;
}
//Handle mouse event of close indicator
mouseAction(over: boolean): void {
this.mouseOn = over;
}
//Show the results
show(): void {
this.stateIndicator = true;
this.searchTrigger.searchInputStat(true);
}
//Close the result page
close(): void {
//Tell shell close
this.searchTrigger.closeSearch(true);
this.searchTrigger.searchInputStat(false);
this.stateIndicator = false;
this.searchTrigger.clear(true);
}
//Call search service to complete the search request
doSearch(term: string): void {
//Only search none empty term
if (!term || term.trim() === "") {
return;
}
//Do nothing if search is ongoing
if (this.onGoing) {
return;

View File

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

View File

@ -8,7 +8,7 @@
<search-result></search-result>
<router-outlet></router-outlet>
</div>
<nav class="sidenav" *ngIf="isUserExisting" [class.side-nav-override]="showSearch" (click)='watchClickEvt()'>
<nav class="sidenav" *ngIf="isUserExisting">
<section class="sidenav-content">
<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>

View File

@ -5,13 +5,11 @@ import { ModalEvent } from '../modal-event';
import { modalEvents } from '../modal-events.const';
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 { NavigatorComponent } from '../navigator/navigator.component';
import { SessionService } from '../../shared/session.service';
import { AboutDialogComponent } from '../../shared/about-dialog/about-dialog.component';
import { StartPageComponent } from '../start-page/start.component';
import { SearchTriggerService } from '../global-search/search-trigger.service';
@ -30,9 +28,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
@ViewChild(AccountSettingsModalComponent)
private accountSettingsModal: AccountSettingsModalComponent;
@ViewChild(SearchResultComponent)
private searchResultComponet: SearchResultComponent;
@ViewChild(PasswordSettingComponent)
private pwdSetting: PasswordSettingComponent;
@ -42,9 +37,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
@ViewChild(AboutDialogComponent)
private aboutDialog: AboutDialogComponent;
@ViewChild(StartPageComponent)
private searchSatrt: StartPageComponent;
//To indicator whwther or not the search results page is displayed
//We need to use this property to do some overriding work
private isSearchResultsOpened: boolean = false;
@ -60,15 +52,13 @@ export class HarborShellComponent implements OnInit, OnDestroy {
ngOnInit() {
this.searchSub = this.searchTrigger.searchTriggerChan$.subscribe(searchEvt => {
this.doSearch(searchEvt);
if(searchEvt && searchEvt.trim() != ""){
this.isSearchResultsOpened = true;
}
});
this.searchCloseSub = this.searchTrigger.searchCloseChan$.subscribe(close => {
if (close) {
this.searchClose();
}else{
this.watchClickEvt();//reuse
}
this.isSearchResultsOpened = false;
});
}
@ -116,30 +106,4 @@ export class HarborShellComponent implements OnInit, OnDestroy {
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);
});
//Confirm search result panel is close
this.searchTrigger.closeSearch(false);
this.searchTrigger.closeSearch(true);
}
//Switch languages
@ -132,10 +132,10 @@ export class NavigatorComponent implements OnInit {
}
//Confirm search result panel is close
this.searchTrigger.closeSearch(false);
this.searchTrigger.closeSearch(true);
}
registryAction(): void {
this.searchTrigger.closeSearch(false);
this.searchTrigger.closeSearch(true);
}
}

View File

@ -125,7 +125,7 @@
</div>
<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>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.AUTH_MODE' | translate}}</span>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.PRO_CREATION_RESTRICTION' | translate}}</span>
</a>
</div>
<div class="form-group">

View File

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

View File

@ -35,8 +35,8 @@ export class RecentLogComponent implements OnInit {
}
private handleOnchange($event: any) {
if (event && event.target && event.srcElement["value"]) {
this.lines = event.srcElement["value"];
if ($event && $event.target && $event.target["value"]) {
this.lines = $event.target["value"];
if (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.CREATION_TIME' | 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">
<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)="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>

View File

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

View File

@ -1,5 +1,7 @@
<a style="display: block;" [routerLink]="['/harbor', 'projects']">&lt; {{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
<h1 class="sub-header-title">{{currentProject.name}}</h1>
<a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects']">&lt; {{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
<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">
<ul class="nav">
<li class="nav-item">

View File

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

View File

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

View File

@ -31,4 +31,5 @@ export class Project {
repo_count: number;
has_project_admin_role: 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'});
} else {
this.createEditDestinationOpened = false;
this.targetForm.reset();
}
}

View File

@ -1,19 +1,19 @@
<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-action-overflow *ngIf="listFullMode && hasProjectAdminRole">
<button class="action-item">{{'REPOSITORY.COPY_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>
</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>{{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-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-action-overflow [hidden]="!hasProjectAdminRole">
<button class="action-item">{{'REPOSITORY.COPY_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>
</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>{{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

@ -1,13 +1,9 @@
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 { State } from 'clarity-angular';
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({
selector: 'list-repository',
@ -17,7 +13,7 @@ export class ListRepositoryComponent implements OnInit {
@Input() projectId: number;
@Input() repositories: Repository[];
@Output() delete = new EventEmitter<string>();
@ -25,17 +21,15 @@ export class ListRepositoryComponent implements OnInit {
@Input() totalRecordCount: number;
@Output() paginate = new EventEmitter<State>();
@Input() mode: string = ListMode.FULL;
@Input() hasProjectAdminRole: boolean;
pageOffset: number = 1;
constructor(
private router: Router,
private searchTrigger: SearchTriggerService,
private session: SessionService) { }
private searchTrigger: SearchTriggerService) { }
ngOnInit() {}
ngOnInit() { }
deleteRepo(repoName: string) {
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 {
this.searchTrigger.closeSearch(false);
this.searchTrigger.closeSearch(true);
let linkUrl = ['harbor', 'tags', projectId, repoName];
if (!this.session.getCurrentUser()) {
let navigatorExtra: NavigationExtras = {
queryParams: { "guest": true }
};
this.router.navigate(linkUrl, navigatorExtra);
} else {
this.router.navigate(linkUrl);
}
this.router.navigate(linkUrl);
}
}

View File

@ -16,11 +16,6 @@ import { State } from 'clarity-angular';
import { Project } from '../project/project';
const repositoryTypes = [
{ key: '0', description: 'REPOSITORY.MY_REPOSITORY' },
{ key: '1', description: 'REPOSITORY.PUBLIC_REPOSITORY' }
];
@Component({
moduleId: module.id,
selector: 'repository',
@ -31,8 +26,7 @@ export class RepositoryComponent implements OnInit {
changedRepositories: Repository[];
projectId: number;
repositoryTypes = repositoryTypes;
currentRepositoryType: {};
lastFilteredRepoName: string;
page: number = 1;
@ -80,7 +74,6 @@ export class RepositoryComponent implements OnInit {
if(resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
}
this.currentRepositoryType = this.repositoryTypes[0];
this.lastFilteredRepoName = '';
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) {
this.lastFilteredRepoName = repoName;
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>
<clr-datagrid>
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>

View File

@ -36,6 +36,8 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
registryUrl: string;
withNotary: boolean;
hasSignedIn: boolean;
private subscription: Subscription;
constructor(
@ -74,8 +76,8 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
}
ngOnInit() {
this.hasSignedIn = (this.session.getCurrentUser() !== null);
let resolverData = this.route.snapshot.data;
console.log(JSON.stringify(resolverData));
if(resolverData) {
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>
</div>
<div>
<list-repository [repositories]="topRepos" [mode]="listMode"></list-repository>
<list-repository-ro [repositories]="topRepos"></list-repository-ro>
</div>
</div>

View File

@ -29,8 +29,8 @@ export class FilterComponent implements OnInit{
ngOnInit(): void {
this.filterTerms
.debounceTime(300)
.distinctUntilChanged()
.debounceTime(500)
//.distinctUntilChanged()
.subscribe(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 { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, Validators } from '@angular/forms';
export const portNumbers = /[\d]+/;
export const portNumbers = /^[\d]{1,5}$/;
export function portValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {

View File

@ -19,15 +19,10 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
private appConfigService: AppConfigService) { }
private isGuest(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let queryParams = route.queryParams;
if (queryParams) {
if (queryParams["guest"]) {
let proRegExp = /\/harbor\/projects\/[\d]+\/.+/i;
const libRegExp = /\/harbor\/tags\/[\d]+\/.+/i;
if (proRegExp.test(state.url)|| libRegExp.test(state.url)) {
return true;
}
}
const proRegExp = /\/harbor\/projects\/[\d]+\/.+/i;
const libRegExp = /\/harbor\/tags\/[\d]+\/.+/i;
if (proRegExp.test(state.url) || libRegExp.test(state.url)) {
return true;
}
return false;
@ -42,8 +37,8 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
this.appConfigService.saveAdmiralEndpoint(queryParams[AdmiralQueryParamKey]);
//Remove the query parameter key pair and redirect
let keyRemovedUrl = maintainUrlQueryParmas(state.url, AdmiralQueryParamKey, undefined);
if(!/[?]{1}.+/i.test(keyRemovedUrl)){
keyRemovedUrl = keyRemovedUrl.replace('?','');
if (!/[?]{1}.+/i.test(keyRemovedUrl)) {
keyRemovedUrl = keyRemovedUrl.replace('?', '');
}
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 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 { 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({
imports: [
CoreModule,
@ -53,7 +56,9 @@ import { MemberGuard } from './route/member-guard-activate.service';
PageNotFoundComponent,
AboutDialogComponent,
StatisticsComponent,
StatisticsPanelComponent
StatisticsPanelComponent,
ListProjectROComponent,
ListRepositoryROComponent
],
exports: [
CoreModule,
@ -70,7 +75,9 @@ import { MemberGuard } from './route/member-guard-activate.service';
PageNotFoundComponent,
AboutDialogComponent,
StatisticsComponent,
StatisticsPanelComponent
StatisticsPanelComponent,
ListProjectROComponent,
ListRepositoryROComponent
],
providers: [
SessionService,