mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-18 22:57:38 +01:00
Merge pull request #12374 from AllForNothing/proxy-cache
Add proxy cache ui
This commit is contained in:
commit
7dfab5858c
@ -41,7 +41,7 @@
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<div class="clr-control-container" [class.clr-error]="(projectStorageLimit.invalid && (projectStorageLimit.dirty || projectStorageLimit.touched))||projectStorageLimit.errors">
|
||||
<input type="text" id="create_project_storage_limit" [(ngModel)]="storageLimit" name="create_project_storage_limit" class="mr-10 clr-input"
|
||||
<input type="text" id="create_project_storage_limit" [(ngModel)]="storageLimit" name="create_project_storage_limit" class="mr-10 clr-input width-182"
|
||||
#projectStorageLimit="ngModel" autocomplete="off">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<div class="clr-select-wrapper">
|
||||
@ -58,6 +58,45 @@
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control" *ngIf="isSystemAdmin">
|
||||
<label for="create_project_storage_limit" class="clr-control-label">{{ 'PROJECT.PROXY_CACHE' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="bottom-left" clrSize="lg" *clrIfOpen>
|
||||
<span>{{ 'PROJECT.PROXY_CACHE_TOOLTIP' | translate }}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<clr-toggle-wrapper class="mt-02">
|
||||
<input type="checkbox" clrToggle name="proxy-cache" id="proxy-cache"
|
||||
[(ngModel)]="enableProxyCache"/>
|
||||
</clr-toggle-wrapper>
|
||||
<div *ngIf="enableProxyCache" class="clr-select-wrapper ml-1">
|
||||
<select class="width-164" id="registry" name="registry" [(ngModel)]="project.registry_id">
|
||||
<option class="display-none" value=""></option>
|
||||
<option *ngFor="let r of registries" [value]="r.id">{{r.name}}-{{r.url}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control mt-0" *ngIf="isSystemAdmin && enableProxyCache">
|
||||
<label for="create_project_storage_limit" class="clr-control-label"></label>
|
||||
<div class="clr-control-container input-width">
|
||||
<div class="space-between" *ngIf=" enableProxyCache && !registries.length" >
|
||||
<span class="alert-label">{{"REPLICATION.NO_ENDPOINT_INFO" | translate}}</span>
|
||||
<a class="alert-label go-link" routerLink="/harbor/registries">{{'REPLICATION.ENDPOINTS' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control mt-05" *ngIf="isSystemAdmin && enableProxyCache">
|
||||
<label for="create_project_storage_limit" class="clr-control-label"></label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<label class="clr-control-label endpoint">{{ 'PROJECT.ENDPOINT' | translate }}</label>
|
||||
<input placeholder="http(s)://192.168.1.1" [value]="getEndpoint()" readonly class="clr-input" type="text" id="endpoint"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -6,11 +6,14 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
||||
import { ProjectService } from "../../../lib/services";
|
||||
import { EndpointDefaultService, EndpointService, ProjectService } from '../../../lib/services';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { ErrorHandler } from '../../../lib/utils/error-handler';
|
||||
import { IServiceConfig, SERVICE_CONFIG } from '../../../lib/entities/service.config';
|
||||
import { CURRENT_BASE_HREF } from '../../../lib/utils/utils';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
|
||||
describe('CreateProjectComponent', () => {
|
||||
let component: CreateProjectComponent;
|
||||
@ -31,10 +34,14 @@ describe('CreateProjectComponent', () => {
|
||||
showSuccess: function() {
|
||||
}
|
||||
};
|
||||
const config: IServiceConfig = {
|
||||
systemInfoEndpoint: CURRENT_BASE_HREF + "/endpoints/testing"
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
ClarityModule,
|
||||
@ -46,8 +53,10 @@ describe('CreateProjectComponent', () => {
|
||||
],
|
||||
providers: [
|
||||
TranslateService,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{provide: ProjectService, useValue: mockProjectService},
|
||||
{provide: MessageHandlerService, useValue: mockMessageHandlerService},
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService },
|
||||
ErrorHandler
|
||||
]
|
||||
}).compileComponents();
|
||||
@ -111,4 +120,13 @@ describe('CreateProjectComponent', () => {
|
||||
const modelBody: HTMLDivElement = fixture.nativeElement.querySelector(".modal-body");
|
||||
expect(modelBody).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should enable proxy cache', async () => {
|
||||
component.enableProxyCache = true;
|
||||
component.isSystemAdmin = true;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const endpoint: HTMLDivElement = fixture.nativeElement.querySelector("#endpoint");
|
||||
expect(endpoint).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@ -30,7 +30,7 @@ import { MessageHandlerService } from "../../shared/message-handler/message-hand
|
||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
||||
import { Project } from "../project";
|
||||
import { QuotaUnits, QuotaUnlimited } from "../../../lib/entities/shared.const";
|
||||
import { ProjectService, QuotaHardInterface } from "../../../lib/services";
|
||||
import { Endpoint, EndpointService, ProjectService, QuotaHardInterface } from '../../../lib/services';
|
||||
import { clone, getByte, GetIntegerAndUnit, validateLimit } from "../../../lib/utils/utils";
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ import { clone, getByte, GetIntegerAndUnit, validateLimit } from "../../../lib/u
|
||||
templateUrl: "create-project.component.html",
|
||||
styleUrls: ["create-project.scss"]
|
||||
})
|
||||
export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||
export class CreateProjectComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
|
||||
|
||||
projectForm: NgForm;
|
||||
|
||||
@ -63,6 +63,8 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
isNameExisted: boolean = false;
|
||||
nameTooltipText = "PROJECT.NAME_TOOLTIP";
|
||||
checkOnGoing = false;
|
||||
enableProxyCache: boolean = false;
|
||||
endpoint: string = "";
|
||||
@Output() create = new EventEmitter<boolean>();
|
||||
@Input() quotaObj: QuotaHardInterface;
|
||||
@Input() isSystemAdmin: boolean;
|
||||
@ -70,11 +72,32 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
inlineAlert: InlineAlertComponent;
|
||||
@ViewChild('projectName', {static: false}) projectNameInput: ElementRef;
|
||||
checkNameSubscribe: Subscription;
|
||||
constructor(private projectService: ProjectService,
|
||||
private translateService: TranslateService,
|
||||
private messageHandlerService: MessageHandlerService) { }
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
registries: Endpoint[] = [];
|
||||
supportedRegistryType: string[] = ['docker-hub', 'harbor'];
|
||||
|
||||
constructor(private projectService: ProjectService,
|
||||
private translateService: TranslateService,
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private endpointService: EndpointService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getRegistries();
|
||||
}
|
||||
|
||||
getRegistries() {
|
||||
this.endpointService.getEndpoints()
|
||||
.subscribe(targets => {
|
||||
if (targets && targets.length) {
|
||||
this.registries = targets.filter(item => this.supportedRegistryType.indexOf(item.type) !== -1);
|
||||
}
|
||||
}, error => {
|
||||
this.messageHandlerService.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (!this.checkNameSubscribe) {
|
||||
this.checkNameSubscribe = fromEvent(this.projectNameInput.nativeElement, 'input').pipe(
|
||||
map((e: any) => e.target.value),
|
||||
@ -161,7 +184,7 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
this.isSubmitOnGoing = true;
|
||||
const storageByte = +this.storageLimit === QuotaUnlimited ? this.storageLimit : getByte(+this.storageLimit, this.storageLimitUnit);
|
||||
this.projectService
|
||||
.createProject(this.project.name, this.project.metadata, +storageByte)
|
||||
.createProject(this.project.name, this.project.metadata, +storageByte, this.project.registry_id)
|
||||
.subscribe(
|
||||
status => {
|
||||
this.isSubmitOnGoing = false;
|
||||
@ -184,6 +207,8 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
this.project = new Project();
|
||||
this.hasChanged = false;
|
||||
this.createProjectOpened = true;
|
||||
this.enableProxyCache = false;
|
||||
this.endpoint = "";
|
||||
if (this.currentForm && this.currentForm.controls && this.currentForm.controls["create_project_name"]) {
|
||||
this.currentForm.controls["create_project_name"].reset();
|
||||
}
|
||||
@ -199,5 +224,16 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
|
||||
this.isNameValid &&
|
||||
!this.checkOnGoing;
|
||||
}
|
||||
|
||||
getEndpoint(): string {
|
||||
if (this.registries && this.registries.length && this.project.registry_id) {
|
||||
for (let i = 0; i < this.registries.length; i++) {
|
||||
if (+this.registries[i].id === +this.project.registry_id) {
|
||||
return this.registries[i].url;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,4 +15,38 @@
|
||||
.clr-select-wrapper::after {
|
||||
right: 0.25rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-width {
|
||||
width: 242px;
|
||||
}
|
||||
.mt-02 {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
.width-164 {
|
||||
width: 164px;
|
||||
}
|
||||
.endpoint {
|
||||
display: inline-block;
|
||||
width: 72px;
|
||||
}
|
||||
.display-none {
|
||||
display: none
|
||||
}
|
||||
.space-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.go-link {
|
||||
line-height: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.alert-label {
|
||||
color:red;
|
||||
font-size: 12px;
|
||||
}
|
||||
.width-182 {
|
||||
width: 182px;
|
||||
}
|
||||
.mt-05 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
@ -140,6 +140,7 @@ describe('ChartDetailComponent', () => {
|
||||
"role_name": 'master',
|
||||
"repo_count": 0,
|
||||
"chart_count": 1,
|
||||
"registry_id" : 0,
|
||||
"metadata": {
|
||||
"public": "true",
|
||||
"enable_content_trust": "string",
|
||||
|
@ -9,6 +9,7 @@
|
||||
<clr-dg-column [clrDgField]="'name'">{{'PROJECT.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="accessLevelComparator">{{'PROJECT.ACCESS_LEVEL' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="roleComparator">{{'PROJECT.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="typeComparator">{{'PROJECT.TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="repoCountComparator">{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="withChartMuseum" [clrDgSortBy]="chartCountComparator">{{'PROJECT.CHART_COUNT'| translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="timeComparator">{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
@ -18,6 +19,7 @@
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{ (p.metadata.public === 'true' ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{ roleInfo[p.current_user_role_id]? (roleInfo[p.current_user_role_id] | translate): "-"}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{projectTypeMap[p.registry_id ? 1 : 0]}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="withChartMuseum">{{p.chart_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
|
@ -55,11 +55,16 @@ export class ListProjectComponent implements OnDestroy {
|
||||
timeComparator: Comparator<Project> = new CustomComparator<Project>("creation_time", "date");
|
||||
accessLevelComparator: Comparator<Project> = new CustomComparator<Project>("public", "string");
|
||||
roleComparator: Comparator<Project> = new CustomComparator<Project>("current_user_role_id", "number");
|
||||
typeComparator: Comparator<Project> = new CustomComparator<Project>("registry_id", "number");
|
||||
currentPage = 1;
|
||||
totalCount = 0;
|
||||
pageSize = 15;
|
||||
currentState: State;
|
||||
subscription: Subscription;
|
||||
projectTypeMap: any = {
|
||||
0: "Project",
|
||||
1: "Proxy Cache"
|
||||
};
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
|
@ -1,8 +1,14 @@
|
||||
<a *ngIf="hasSignedIn" (click)="backToProject()" class="backStyle"> {{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
|
||||
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']"> {{'SEARCH.BACK' | translate}}</a>
|
||||
|
||||
<h1 class="custom-h2" sub-header-title>{{currentProject.name}} <span class="role-label"
|
||||
*ngIf="isMember">{{roleName | translate}}</span></h1>
|
||||
<h1 class="custom-h2" sub-header-title>
|
||||
<clr-icon *ngIf="isProxyCacheProject" shape="cloud-traffic" size="30"></clr-icon>
|
||||
<span class="ml-05">{{currentProject.name}}</span>
|
||||
<span class="ml-05 role-label" *ngIf="isMember">{{roleName | translate}}</span>
|
||||
</h1>
|
||||
<div class="clr-row mt-0 line-height-10" *ngIf="isProxyCacheProject">
|
||||
<span class="proxy-cache">{{ 'PROJECT.PROXY_CACHE' | translate }}</span>
|
||||
</div>
|
||||
<clr-tabs id="project-tabs" class="tabs" [class.in-overflow]="isTabLinkInOverFlow()">
|
||||
<ng-container *ngFor="let tab of tabLinkNavList;let i=index">
|
||||
<ng-container *ngIf="tab.permissions()">
|
||||
|
@ -42,3 +42,15 @@ button {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.ml-05 {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.proxy-cache {
|
||||
margin-left: 2.5rem;
|
||||
font-size: 10px;
|
||||
font-weight: 300;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.line-height-10 {
|
||||
line-height: 10px;
|
||||
}
|
||||
|
@ -118,6 +118,7 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
previousWindowWidth: number;
|
||||
private _subject = new Subject<string>();
|
||||
private _subscription: Subscription;
|
||||
isProxyCacheProject: boolean = false;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@ -129,6 +130,9 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
this.hasSignedIn = this.sessionService.getCurrentUser() !== null;
|
||||
this.route.data.subscribe(data => {
|
||||
this.currentProject = <Project>data['projectResolver'];
|
||||
if (this.currentProject.registry_id) {
|
||||
this.isProxyCacheProject = true;
|
||||
}
|
||||
this.isMember = this.currentProject.is_member;
|
||||
this.roleName = this.currentProject.role_name;
|
||||
});
|
||||
|
@ -27,6 +27,7 @@ export class Project {
|
||||
has_project_admin_role: boolean;
|
||||
is_member: boolean;
|
||||
role_name: string;
|
||||
registry_id: number;
|
||||
metadata: {
|
||||
public: string | boolean;
|
||||
enable_content_trust: string | boolean;
|
||||
|
@ -1,18 +1,24 @@
|
||||
<div class="summary summary-dark display-flex" *ngIf="summaryInformation">
|
||||
<div class="summary-left">
|
||||
<div class="display-flex project-detail pt-1">
|
||||
<div class="display-flex project-detail pt-05" *ngIf="isSystemAdmin && endpoint">
|
||||
<h5 class="mt-0 width-7-5">{{'PROJECT.PROXY_CACHE_ENDPOINT' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li id="endpoint">{{endpoint?.name}}-{{endpoint?.url}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="display-flex project-detail pt-05">
|
||||
<h5 class="mt-0 width-7-5">{{'SUMMARY.PROJECT_REPOSITORY' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{summaryInformation?.repo_count}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="display-flex project-detail pt-1" *ngIf="withHelmChart">
|
||||
<div class="display-flex project-detail pt-05" *ngIf="withHelmChart">
|
||||
<h5 class="mt-0 width-7-5">{{'SUMMARY.PROJECT_HELM_CHART' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{summaryInformation?.chart_count}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="showProjectMemberInfo" class="display-flex project-detail pt-1">
|
||||
<div *ngIf="showProjectMemberInfo" class="display-flex project-detail pt-05">
|
||||
<h5 class="mt-0 width-7-5">{{'SUMMARY.PROJECT_MEMBER' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{ summaryInformation?.project_admin_count }} {{'SUMMARY.ADMIN' | translate}}</li>
|
||||
@ -23,7 +29,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showQuotaInfo && summaryInformation?.quota" class="summary-right pt-1">
|
||||
<div *ngIf="showQuotaInfo && summaryInformation?.quota" class="summary-right pt-05">
|
||||
<div class="display-flex project-detail">
|
||||
<h5 class="mt-0">{{'SUMMARY.PROJECT_QUOTAS' | translate}}</h5>
|
||||
<div class="ml-1">
|
||||
@ -55,4 +61,4 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,3 +57,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pt-05 {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
@ -6,14 +6,23 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
import { AppConfigService } from "../../services/app-config.service";
|
||||
import { SummaryComponent } from './summary.component';
|
||||
import { ProjectService, UserPermissionService } from "../../../lib/services";
|
||||
import { EndpointDefaultService, EndpointService, ProjectService, UserPermissionService } from '../../../lib/services';
|
||||
import { ErrorHandler } from "../../../lib/utils/error-handler";
|
||||
import { IServiceConfig, SERVICE_CONFIG } from '../../../lib/entities/service.config';
|
||||
import { CURRENT_BASE_HREF } from '../../../lib/utils/utils';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
|
||||
describe('SummaryComponent', () => {
|
||||
let component: SummaryComponent;
|
||||
let fixture: ComponentFixture<SummaryComponent>;
|
||||
let fakeAppConfigService = null;
|
||||
let fakeAppConfigService = {
|
||||
getConfig() {
|
||||
return {
|
||||
with_chartmuseum: false
|
||||
};
|
||||
}
|
||||
};
|
||||
let fakeProjectService = {
|
||||
getProjectSummary: function () {
|
||||
return of();
|
||||
@ -25,6 +34,34 @@ describe('SummaryComponent', () => {
|
||||
return of([true, true]);
|
||||
}
|
||||
};
|
||||
const config: IServiceConfig = {
|
||||
systemInfoEndpoint: CURRENT_BASE_HREF + "/endpoints/testing"
|
||||
};
|
||||
|
||||
const fakedSessionService = {
|
||||
getCurrentUser() {
|
||||
return {
|
||||
has_admin_role: true
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const fakedEndpointService = {
|
||||
getEndpoint() {
|
||||
return of({
|
||||
name: "test",
|
||||
url: "https://test.com"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const mockedSummaryInformation = {
|
||||
repo_count: 0,
|
||||
chart_count: 0,
|
||||
project_admin_count: 1,
|
||||
master_count: 0,
|
||||
developer_count: 0
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -42,14 +79,19 @@ describe('SummaryComponent', () => {
|
||||
{ provide: ProjectService, useValue: fakeProjectService },
|
||||
{ provide: ErrorHandler, useValue: fakeErrorHandler },
|
||||
{ provide: UserPermissionService, useValue: fakeUserPermissionService },
|
||||
{ provide: EndpointService, useValue: fakedEndpointService },
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: SessionService, useValue: fakedSessionService},
|
||||
{
|
||||
provide: ActivatedRoute, useValue: {
|
||||
paramMap: of({ get: (key) => 'value' }),
|
||||
snapshot: {
|
||||
parent: {
|
||||
params: { id: 1 }
|
||||
params: { id: 1 },
|
||||
data: {
|
||||
projectResolver: {registry_id: 3}
|
||||
}
|
||||
},
|
||||
data: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -66,4 +108,13 @@ describe('SummaryComponent', () => {
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show proxy cache endpoint', async () => {
|
||||
component.summaryInformation = mockedSummaryInformation;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const endpoint: HTMLElement = fixture.nativeElement.querySelector("#endpoint");
|
||||
expect(endpoint).toBeTruthy();
|
||||
expect(endpoint.innerText).toEqual("test-https://test.com");
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,18 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AppConfigService } from "../../services/app-config.service";
|
||||
import { QUOTA_DANGER_COEFFICIENT, QUOTA_WARNING_COEFFICIENT, QuotaUnits } from "../../../lib/entities/shared.const";
|
||||
import { ProjectService, UserPermissionService, USERSTATICPERMISSION } from "../../../lib/services";
|
||||
import {
|
||||
Endpoint,
|
||||
EndpointService,
|
||||
ProjectService,
|
||||
UserPermissionService,
|
||||
USERSTATICPERMISSION
|
||||
} from '../../../lib/services';
|
||||
import { ErrorHandler } from "../../../lib/utils/error-handler";
|
||||
import { clone, GetIntegerAndUnit, getSuitableUnit as getSuitableUnitFn } from "../../../lib/utils/utils";
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { Project } from '../project';
|
||||
|
||||
@Component({
|
||||
selector: 'summary',
|
||||
@ -19,20 +27,30 @@ export class SummaryComponent implements OnInit {
|
||||
summaryInformation: any;
|
||||
quotaDangerCoefficient: number = QUOTA_DANGER_COEFFICIENT;
|
||||
quotaWarningCoefficient: number = QUOTA_WARNING_COEFFICIENT;
|
||||
endpoint: Endpoint;
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private userPermissionService: UserPermissionService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private appConfigService: AppConfigService,
|
||||
private route: ActivatedRoute
|
||||
private route: ActivatedRoute,
|
||||
private session: SessionService,
|
||||
private endpointService: EndpointService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.projectId = this.route.snapshot.parent.params['id'];
|
||||
const resolverData = this.route.snapshot.parent.data;
|
||||
if (resolverData) {
|
||||
const pro: Project = <Project>resolverData['projectResolver'];
|
||||
if (pro && pro.registry_id && this.isSystemAdmin) {
|
||||
this.getRegistry(pro.registry_id);
|
||||
}
|
||||
}
|
||||
|
||||
const permissions = [
|
||||
{ resource: USERSTATICPERMISSION.MEMBER.KEY, action: USERSTATICPERMISSION.MEMBER.VALUE.LIST },
|
||||
{ resource: USERSTATICPERMISSION.QUOTA.KEY, action: USERSTATICPERMISSION.QUOTA.VALUE.READ },
|
||||
{resource: USERSTATICPERMISSION.MEMBER.KEY, action: USERSTATICPERMISSION.MEMBER.VALUE.LIST},
|
||||
{resource: USERSTATICPERMISSION.QUOTA.KEY, action: USERSTATICPERMISSION.QUOTA.VALUE.READ},
|
||||
];
|
||||
|
||||
this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array<boolean>) => {
|
||||
@ -47,6 +65,14 @@ export class SummaryComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
getRegistry(registryId: number) {
|
||||
this.endpointService.getEndpoint(registryId).subscribe(res => {
|
||||
this.endpoint = res;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
getSuitableUnit(value) {
|
||||
const QuotaUnitsCopy = clone(QuotaUnits);
|
||||
return getSuitableUnitFn(value, QuotaUnitsCopy);
|
||||
@ -60,4 +86,9 @@ export class SummaryComponent implements OnInit {
|
||||
return this.appConfigService.getConfig().with_chartmuseum;
|
||||
}
|
||||
|
||||
public get isSystemAdmin(): boolean {
|
||||
const account = this.session.getCurrentUser();
|
||||
return account && account.has_admin_role;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -235,10 +235,15 @@
|
||||
"INLINE_HELP_PUBLIC": "When a project is set to public, anyone has read permission to the repositories under this project, and the user does not need to run \"docker login\" before pulling images under this project.",
|
||||
"OF": "of",
|
||||
"COUNT_QUOTA": "Count quota",
|
||||
"STORAGE_QUOTA": "Storage quota",
|
||||
"STORAGE_QUOTA": "Storage Quota",
|
||||
"COUNT_QUOTA_TIP": "Please enter an integer between '1' & '100,000,000', '-1' for unlimited.",
|
||||
"STORAGE_QUOTA_TIP": "The upper limit of Storage Quota only takes integer values, capped at '1024TB'. Enter '-1' for unlimited quota",
|
||||
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'."
|
||||
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.",
|
||||
"TYPE": "Type",
|
||||
"PROXY_CACHE": "Proxy Cache",
|
||||
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry.Harbor can only act a proxy for DockerHub and Harbor registries.",
|
||||
"ENDPOINT": "Endpoint",
|
||||
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"SUMMARY": "Summary",
|
||||
|
@ -239,7 +239,12 @@
|
||||
"STORAGE_QUOTA": "Storage quota",
|
||||
"COUNT_QUOTA_TIP": "Please enter an integer between '1' & '100,000,000', '-1' for unlimited",
|
||||
"STORAGE_QUOTA_TIP": "The upper limit of Storage Quota only takes integer values, capped at '1024TB'. Enter '-1' for unlimited quota",
|
||||
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'."
|
||||
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.",
|
||||
"TYPE": "Type",
|
||||
"PROXY_CACHE": "Proxy Cache",
|
||||
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry.Harbor can only act a proxy for DockerHub and Harbor registries.",
|
||||
"ENDPOINT": "Endpoint",
|
||||
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"SUMMARY": "Summary",
|
||||
|
@ -232,7 +232,12 @@
|
||||
"STORAGE_QUOTA": "Storage quota",
|
||||
"COUNT_QUOTA_TIP": "Please enter an integer between '1' & '100,000,000', '-1' for unlimited",
|
||||
"STORAGE_QUOTA_TIP": "The upper limit of Storage Quota only takes integer values, capped at '1024TB'. Enter '-1' for unlimited quota",
|
||||
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'."
|
||||
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.",
|
||||
"TYPE": "Type",
|
||||
"PROXY_CACHE": "Proxy Cache",
|
||||
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry.Harbor can only act a proxy for DockerHub and Harbor registries.",
|
||||
"ENDPOINT": "Endpoint",
|
||||
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"SUMMARY": "Summary",
|
||||
|
@ -236,7 +236,12 @@
|
||||
"STORAGE_QUOTA": "Storage quota",
|
||||
"COUNT_QUOTA_TIP": "Please enter an integer between '1' & '100,000,000', '-1' for unlimited",
|
||||
"STORAGE_QUOTA_TIP": "The upper limit of Storage Quota only takes integer values, capped at '1024TB'. Enter '-1' for unlimited quota",
|
||||
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'."
|
||||
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.",
|
||||
"TYPE": "Type",
|
||||
"PROXY_CACHE": "Proxy Cache",
|
||||
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry.Harbor can only act a proxy for DockerHub and Harbor registries.",
|
||||
"ENDPOINT": "Endpoint",
|
||||
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"SUMMARY": "Summary",
|
||||
|
@ -238,7 +238,12 @@
|
||||
"STORAGE_QUOTA": "Depolama kotası",
|
||||
"COUNT_QUOTA_TIP": "Sayı Kotasının üst sınırı tamsayı olmalıdır.\n",
|
||||
"STORAGE_QUOTA_TIP": "Depolama Kotasının üst sınırı tamsayı olmalı ve maksimum üst sınır 1024 TB",
|
||||
"QUOTA_UNLIMIT_TIP": "Bu kotayı sınırsız istiyorsanız, lütfen -1 girin."
|
||||
"QUOTA_UNLIMIT_TIP": "Bu kotayı sınırsız istiyorsanız, lütfen -1 girin.",
|
||||
"TYPE": "Type",
|
||||
"PROXY_CACHE": "Proxy Cache",
|
||||
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry.Harbor can only act a proxy for DockerHub and Harbor registries.",
|
||||
"ENDPOINT": "Endpoint",
|
||||
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"SUMMARY": "Özet",
|
||||
|
@ -237,7 +237,12 @@
|
||||
"STORAGE_QUOTA": "存储容量",
|
||||
"COUNT_QUOTA_TIP": "请输入一个'1' ~ '100000000'之间的整数, '-1'表示不设置上限。",
|
||||
"STORAGE_QUOTA_TIP": "存储配额的上限仅采用整数值,上限为1024TB。输入“-1”作为无限制配额。",
|
||||
"QUOTA_UNLIMIT_TIP": "如果你想要对存储不设置上限,请输入-1。"
|
||||
"QUOTA_UNLIMIT_TIP": "如果你想要对存储不设置上限,请输入-1。",
|
||||
"TYPE": "类型",
|
||||
"PROXY_CACHE": "镜像代理",
|
||||
"PROXY_CACHE_TOOLTIP": "开启此项,以使得该项目成为目标仓库的镜像代理.仅支持 Docker Hub 和 Harbor 类型的仓库",
|
||||
"ENDPOINT": "地址",
|
||||
"PROXY_CACHE_ENDPOINT": "镜像代理地址"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"SUMMARY": "概要",
|
||||
|
@ -235,7 +235,12 @@
|
||||
"STORAGE_QUOTA": "存儲容量",
|
||||
"COUNT_QUOTA_TIP": "請輸入一個'1' ~ '100000000'之間的整數, '-1'表示不設置上限。",
|
||||
"STORAGE_QUOTA_TIP": "存儲配額的上限僅採用整數值,上限為1024TB。輸入“-1”作為無限製配額。",
|
||||
"QUOTA_UNLIMIT_TIP": "如果你想要對存儲不設置上限,請輸入-1。"
|
||||
"QUOTA_UNLIMIT_TIP": "如果你想要對存儲不設置上限,請輸入-1。",
|
||||
"TYPE": "Type",
|
||||
"PROXY_CACHE": "Proxy Cache",
|
||||
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry.Harbor can only act a proxy for DockerHub and Harbor registries.",
|
||||
"ENDPOINT": "Endpoint",
|
||||
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
|
||||
},
|
||||
"PROJECT_DETAIL":{
|
||||
"SUMMARY": "概要",
|
||||
|
@ -69,7 +69,7 @@ export abstract class ProjectService {
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
): Observable<HttpResponse<Project[]>>;
|
||||
abstract createProject(name: string, metadata: any, storageLimit: number): Observable<any>;
|
||||
abstract createProject(name: string, metadata: any, storageLimit: number, registryId: number): Observable<any>;
|
||||
abstract deleteProject(projectId: number): Observable<any>;
|
||||
abstract checkProjectExists(projectName: string): Observable<any>;
|
||||
abstract checkProjectMember(projectId: number): Observable<any>;
|
||||
@ -149,12 +149,13 @@ export class ProjectDefaultService extends ProjectService {
|
||||
catchError(error => observableThrowError(error)), );
|
||||
}
|
||||
|
||||
public createProject(name: string, metadata: any, storageLimit: number): Observable<any> {
|
||||
public createProject(name: string, metadata: any, storageLimit: number, registryId: number): Observable<any> {
|
||||
return this.http
|
||||
.post(`${ CURRENT_BASE_HREF }/projects`,
|
||||
JSON.stringify({'project_name': name, 'metadata': {
|
||||
public: metadata.public ? 'true' : 'false',
|
||||
},
|
||||
JSON.stringify({
|
||||
'project_name': name, registry_id: +registryId, 'metadata': {
|
||||
public: metadata.public ? 'true' : 'false'
|
||||
},
|
||||
storage_limit: storageLimit
|
||||
})
|
||||
, HTTP_JSON_OPTIONS).pipe(
|
||||
|
Loading…
Reference in New Issue
Block a user