Add proxy cache ui

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
AllForNothing 2020-07-01 17:55:26 +08:00
parent 468ba50a7e
commit a13642c2af
23 changed files with 325 additions and 39 deletions

View File

@ -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">

View File

@ -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();
});
});

View File

@ -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 '';
}
}

View File

@ -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;
}

View File

@ -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",

View File

@ -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>

View File

@ -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,

View File

@ -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()">

View File

@ -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;
}

View File

@ -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;
});

View File

@ -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;

View File

@ -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>

View File

@ -57,3 +57,7 @@
}
}
}
.pt-05 {
padding-top: 0.5rem;
}

View File

@ -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");
});
});

View File

@ -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;
}
}

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "概要",

View File

@ -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": "概要",

View File

@ -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(