mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-26 18:48:02 +01:00
Merge pull request #1791 from wknet123/dev-revised
Merge latest updates of UI.
This commit is contained in:
commit
28a513f900
@ -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
|
||||
|
@ -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>
|
@ -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 {
|
||||
|
@ -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>
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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">
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
<a style="display: block;" [routerLink]="['/harbor', 'projects']">< {{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
|
||||
<h1 class="sub-header-title">{{currentProject.name}}</h1>
|
||||
<a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects']">< {{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
|
||||
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">< {{'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">
|
||||
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -1,9 +1,9 @@
|
||||
.header-title {
|
||||
margin-top: 12px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.option-left {
|
||||
padding-left: 12px;
|
||||
padding-left: 16px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
|
@ -31,4 +31,5 @@ export class Project {
|
||||
repo_count: number;
|
||||
has_project_admin_role: boolean;
|
||||
is_member: boolean;
|
||||
role_name: string;
|
||||
}
|
@ -174,6 +174,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
this.inlineAlert.showInlineConfirmation({message: 'ALERT.FORM_CHANGE_CONFIRMATION'});
|
||||
} else {
|
||||
this.createEditDestinationOpened = false;
|
||||
this.targetForm.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -1,4 +1,6 @@
|
||||
<a [routerLink]="['/harbor', 'projects', projectId, 'repository']">< {{'REPOSITORY.REPOSITORIES' | translate}}</a>
|
||||
<a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects', projectId, 'repository']">< {{'REPOSITORY.REPOSITORIES' | translate}}</a>
|
||||
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">< {{'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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
@ -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);
|
||||
});
|
||||
|
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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 } => {
|
||||
|
@ -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);
|
||||
|
@ -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' };
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user