mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-24 19:25:19 +01:00
Fixes-21021 Display proxy cache in project configuration page (#21048)
Signed-off-by: xuelichao <xuel@vmware.com> Co-authored-by: Lichao Xue <lichao.xue@broadcom.com>
This commit is contained in:
parent
4a5185995e
commit
5ac9733396
@ -16,6 +16,84 @@
|
|||||||
{{ 'PROJECT_CONFIG.PUBLIC_POLICY' | translate }}
|
{{ 'PROJECT_CONFIG.PUBLIC_POLICY' | translate }}
|
||||||
</clr-control-helper>
|
</clr-control-helper>
|
||||||
</clr-checkbox-container>
|
</clr-checkbox-container>
|
||||||
|
<clr-checkbox-container *ngIf="isSystemAdmin" clrInline>
|
||||||
|
<label class="label-color-input">
|
||||||
|
{{ 'PROJECT.PROXY_CACHE' | translate }}
|
||||||
|
</label>
|
||||||
|
<clr-checkbox-wrapper id="is-project-proxy-cache-enabled">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
clrCheckbox
|
||||||
|
disabled="!allowUpdateProxyCacheConfiguration"
|
||||||
|
[(ngModel)]="projectPolicy.ProxyCacheEnabled"
|
||||||
|
name="project-proxy-cache-enabled" />
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<clr-control-helper class="config-subtext">
|
||||||
|
{{ 'PROJECT.PROXY_CACHE_TOOLTIP' | translate }}
|
||||||
|
</clr-control-helper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
<div
|
||||||
|
*ngIf="isSystemAdmin && projectPolicy.ProxyCacheEnabled"
|
||||||
|
class="clr-form-control mt-0">
|
||||||
|
<label class="clr-control-label"></label>
|
||||||
|
<div class="clr-select-wrapper row-inline">
|
||||||
|
<label class="clr-control-label">
|
||||||
|
{{ 'PROJECT.ENDPOINT' | translate }}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
class="width-164 ml-1"
|
||||||
|
id="registry"
|
||||||
|
name="registry"
|
||||||
|
disabled="!allowUpdateProxyCacheConfiguration"
|
||||||
|
[(ngModel)]="projectPolicy.RegistryId">
|
||||||
|
<option class="display-none" value=""></option>
|
||||||
|
<option *ngFor="let r of registries" [value]="r.id">
|
||||||
|
{{ r.name }}-{{ r.url }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row-inline ml-2" clrInline>
|
||||||
|
<label class="clr-control-label">
|
||||||
|
{{ 'PROJECT.BANDWIDTH' | translate }}
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="clr-control-container ml-1"
|
||||||
|
[class.clr-error]="bandwidthError">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="bandwidth"
|
||||||
|
[(ngModel)]="projectPolicy.ProxySpeedKb"
|
||||||
|
name="bandwidth"
|
||||||
|
disabled="!allowUpdateProxyCacheConfiguration"
|
||||||
|
class="clr-input width-164 mr-10 clr-input-underline"
|
||||||
|
autocomplete="off"
|
||||||
|
(ngModelChange)="validateBandwidth()" />
|
||||||
|
<clr-icon
|
||||||
|
*ngIf="bandwidthError"
|
||||||
|
class="clr-validate-icon"
|
||||||
|
shape="exclamation-circle"></clr-icon>
|
||||||
|
<div class="clr-select-wrapper mr-10 margin-left-05">
|
||||||
|
<select
|
||||||
|
id="bandwidth_unit"
|
||||||
|
name="bandwidth_unit"
|
||||||
|
disabled="!allowUpdateProxyCacheConfiguration"
|
||||||
|
[(ngModel)]="speedUnit">
|
||||||
|
<option
|
||||||
|
*ngFor="let unit of speedUnits"
|
||||||
|
[value]="unit.UNIT">
|
||||||
|
{{ unit.UNIT }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<clr-control-error
|
||||||
|
*ngIf="bandwidthError"
|
||||||
|
class="tooltip-content">
|
||||||
|
{{ 'PROJECT.SPEED_LIMIT_TIP' | translate }}
|
||||||
|
</clr-control-error>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<clr-checkbox-container *ngIf="!isProxyCacheProject" clrInline>
|
<clr-checkbox-container *ngIf="!isProxyCacheProject" clrInline>
|
||||||
<label
|
<label
|
||||||
><span>{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label
|
><span>{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label
|
||||||
|
@ -97,3 +97,15 @@
|
|||||||
.margin-top-05 {
|
.margin-top-05 {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.margin-left-05 {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-inline {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-inline label {
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import { of } from 'rxjs';
|
|||||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||||
|
import { SessionService } from '../../../../shared/services/session.service';
|
||||||
import { Component, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
|
|
||||||
const mockSystemInfo: SystemInfo[] = [
|
const mockSystemInfo: SystemInfo[] = [
|
||||||
@ -99,6 +100,14 @@ const userPermissionService = {
|
|||||||
return of(true);
|
return of(true);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sessionService = {
|
||||||
|
getCurrentUser() {
|
||||||
|
return of({
|
||||||
|
has_admin_role: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
describe('ProjectPolicyConfigComponent', () => {
|
describe('ProjectPolicyConfigComponent', () => {
|
||||||
let fixture: ComponentFixture<TestHostComponent>,
|
let fixture: ComponentFixture<TestHostComponent>,
|
||||||
component: TestHostComponent;
|
component: TestHostComponent;
|
||||||
@ -114,6 +123,7 @@ describe('ProjectPolicyConfigComponent', () => {
|
|||||||
{ provide: ErrorHandler, useClass: MessageHandlerService },
|
{ provide: ErrorHandler, useClass: MessageHandlerService },
|
||||||
{ provide: ProjectService, useValue: projectService },
|
{ provide: ProjectService, useValue: projectService },
|
||||||
{ provide: SystemInfoService, useValue: systemInfoService },
|
{ provide: SystemInfoService, useValue: systemInfoService },
|
||||||
|
{ provide: SessionService, useValue: sessionService },
|
||||||
{
|
{
|
||||||
provide: UserPermissionService,
|
provide: UserPermissionService,
|
||||||
useValue: userPermissionService,
|
useValue: userPermissionService,
|
||||||
|
@ -4,6 +4,7 @@ import { ProjectService } from '../../../../shared/services';
|
|||||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||||
import { State, SystemCVEAllowlist } from '../../../../shared/services';
|
import { State, SystemCVEAllowlist } from '../../../../shared/services';
|
||||||
import {
|
import {
|
||||||
|
BandwidthUnit,
|
||||||
ConfirmationState,
|
ConfirmationState,
|
||||||
ConfirmationTargets,
|
ConfirmationTargets,
|
||||||
} from '../../../../shared/entities/shared.const';
|
} from '../../../../shared/entities/shared.const';
|
||||||
@ -15,10 +16,15 @@ import { Project } from './project';
|
|||||||
import { SystemInfo, SystemInfoService } from '../../../../shared/services';
|
import { SystemInfo, SystemInfoService } from '../../../../shared/services';
|
||||||
import { UserPermissionService } from '../../../../shared/services';
|
import { UserPermissionService } from '../../../../shared/services';
|
||||||
import { USERSTATICPERMISSION } from '../../../../shared/services';
|
import { USERSTATICPERMISSION } from '../../../../shared/services';
|
||||||
|
import { SessionService } from '../../../../shared/services/session.service';
|
||||||
|
import { Registry } from '../../../../../../ng-swagger-gen/models/registry';
|
||||||
import {
|
import {
|
||||||
EventService,
|
EventService,
|
||||||
HarborEvent,
|
HarborEvent,
|
||||||
} from '../../../../services/event-service/event.service';
|
} from '../../../../services/event-service/event.service';
|
||||||
|
import { forkJoin, Observable } from 'rxjs';
|
||||||
|
import { MessageHandlerService } from 'src/app/shared/services/message-handler.service';
|
||||||
|
import { RegistryService } from 'ng-swagger-gen/services';
|
||||||
|
|
||||||
const ONE_THOUSAND: number = 1000;
|
const ONE_THOUSAND: number = 1000;
|
||||||
const LOW: string = 'low';
|
const LOW: string = 'low';
|
||||||
@ -33,6 +39,9 @@ export class ProjectPolicy {
|
|||||||
PreventVulImgSeverity: string;
|
PreventVulImgSeverity: string;
|
||||||
ScanImgOnPush: boolean;
|
ScanImgOnPush: boolean;
|
||||||
GenerateSbomOnPush: boolean;
|
GenerateSbomOnPush: boolean;
|
||||||
|
ProxyCacheEnabled: boolean;
|
||||||
|
RegistryId?: number | null;
|
||||||
|
ProxySpeedKb?: number | null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.Public = false;
|
this.Public = false;
|
||||||
@ -42,6 +51,9 @@ export class ProjectPolicy {
|
|||||||
this.PreventVulImgSeverity = LOW;
|
this.PreventVulImgSeverity = LOW;
|
||||||
this.ScanImgOnPush = false;
|
this.ScanImgOnPush = false;
|
||||||
this.GenerateSbomOnPush = false;
|
this.GenerateSbomOnPush = false;
|
||||||
|
this.ProxyCacheEnabled = false;
|
||||||
|
this.RegistryId = null;
|
||||||
|
this.ProxySpeedKb = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
initByProject(pro: Project) {
|
initByProject(pro: Project) {
|
||||||
@ -55,8 +67,14 @@ export class ProjectPolicy {
|
|||||||
}
|
}
|
||||||
this.ScanImgOnPush = pro.metadata.auto_scan === 'true';
|
this.ScanImgOnPush = pro.metadata.auto_scan === 'true';
|
||||||
this.GenerateSbomOnPush = pro.metadata.auto_sbom_generation === 'true';
|
this.GenerateSbomOnPush = pro.metadata.auto_sbom_generation === 'true';
|
||||||
|
this.ProxyCacheEnabled = pro.registry_id ? true : false;
|
||||||
|
this.RegistryId = pro.registry_id;
|
||||||
|
this.ProxySpeedKb = pro.metadata.proxy_speed_kb
|
||||||
|
? pro.metadata.proxy_speed_kb
|
||||||
|
: -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const PAGE_SIZE: number = 100;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hbr-project-policy-config',
|
selector: 'hbr-project-policy-config',
|
||||||
@ -65,6 +83,7 @@ export class ProjectPolicy {
|
|||||||
})
|
})
|
||||||
export class ProjectPolicyConfigComponent implements OnInit {
|
export class ProjectPolicyConfigComponent implements OnInit {
|
||||||
onGoing = false;
|
onGoing = false;
|
||||||
|
allowUpdateProxyCacheConfiguration = false;
|
||||||
@Input() projectId: number;
|
@Input() projectId: number;
|
||||||
@Input() projectName = 'unknown';
|
@Input() projectName = 'unknown';
|
||||||
@Input() isProxyCacheProject: boolean = false;
|
@Input() isProxyCacheProject: boolean = false;
|
||||||
@ -81,6 +100,7 @@ export class ProjectPolicyConfigComponent implements OnInit {
|
|||||||
orgProjectPolicy = new ProjectPolicy();
|
orgProjectPolicy = new ProjectPolicy();
|
||||||
projectPolicy = new ProjectPolicy();
|
projectPolicy = new ProjectPolicy();
|
||||||
hasChangeConfigRole: boolean;
|
hasChangeConfigRole: boolean;
|
||||||
|
|
||||||
severityOptions = [
|
severityOptions = [
|
||||||
{
|
{
|
||||||
severity: 'critical',
|
severity: 'critical',
|
||||||
@ -102,6 +122,20 @@ export class ProjectPolicyConfigComponent implements OnInit {
|
|||||||
systemAllowlistOrProjectAllowlistOrigin: string;
|
systemAllowlistOrProjectAllowlistOrigin: string;
|
||||||
projectAllowlist;
|
projectAllowlist;
|
||||||
projectAllowlistOrigin;
|
projectAllowlistOrigin;
|
||||||
|
speedUnit = BandwidthUnit.KB;
|
||||||
|
speedUnits = [
|
||||||
|
{
|
||||||
|
UNIT: BandwidthUnit.KB,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UNIT: BandwidthUnit.MB,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// **Added property for bandwidth error message**
|
||||||
|
bandwidthError: string | null = null;
|
||||||
|
registries: Registry[] = [];
|
||||||
|
supportedRegistryTypeQueryString: string =
|
||||||
|
'type={docker-hub harbor azure-acr aws-ecr google-gcr quay docker-registry github-ghcr jfrog-artifactory}';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private errorHandler: ErrorHandler,
|
private errorHandler: ErrorHandler,
|
||||||
@ -109,6 +143,9 @@ export class ProjectPolicyConfigComponent implements OnInit {
|
|||||||
private projectService: ProjectService,
|
private projectService: ProjectService,
|
||||||
private systemInfoService: SystemInfoService,
|
private systemInfoService: SystemInfoService,
|
||||||
private userPermission: UserPermissionService,
|
private userPermission: UserPermissionService,
|
||||||
|
private session: SessionService,
|
||||||
|
private messageHandlerService: MessageHandlerService,
|
||||||
|
private endpointService: RegistryService,
|
||||||
private event: EventService
|
private event: EventService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -135,6 +172,74 @@ export class ProjectPolicyConfigComponent implements OnInit {
|
|||||||
this.retrieve();
|
this.retrieve();
|
||||||
this.getPermission();
|
this.getPermission();
|
||||||
this.getSystemAllowlist();
|
this.getSystemAllowlist();
|
||||||
|
if (this.isSystemAdmin) {
|
||||||
|
this.getRegistries();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateBandwidth(): void {
|
||||||
|
const value = Number(this.projectPolicy.ProxySpeedKb);
|
||||||
|
if (
|
||||||
|
isNaN(value) ||
|
||||||
|
(!Number.isInteger(value) && value !== -1) ||
|
||||||
|
(value <= 0 && value !== -1)
|
||||||
|
) {
|
||||||
|
this.bandwidthError =
|
||||||
|
'Please enter -1 or an integer greater than 0.';
|
||||||
|
} else {
|
||||||
|
this.bandwidthError = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegistries() {
|
||||||
|
this.endpointService
|
||||||
|
.listRegistriesResponse({
|
||||||
|
page: 1,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
q: this.supportedRegistryTypeQueryString,
|
||||||
|
})
|
||||||
|
.subscribe(
|
||||||
|
result => {
|
||||||
|
// Get total count
|
||||||
|
if (result.headers) {
|
||||||
|
const xHeader: string =
|
||||||
|
result.headers.get('X-Total-Count');
|
||||||
|
const totalCount = parseInt(xHeader, 0);
|
||||||
|
let arr = result.body || [];
|
||||||
|
if (totalCount <= PAGE_SIZE) {
|
||||||
|
// already gotten all Registries
|
||||||
|
this.registries = result.body || [];
|
||||||
|
} else {
|
||||||
|
// get all the registries in specified times
|
||||||
|
const times: number = Math.ceil(
|
||||||
|
totalCount / PAGE_SIZE
|
||||||
|
);
|
||||||
|
const observableList: Observable<Registry[]>[] = [];
|
||||||
|
for (let i = 2; i <= times; i++) {
|
||||||
|
observableList.push(
|
||||||
|
this.endpointService.listRegistries({
|
||||||
|
page: i,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
q: this
|
||||||
|
.supportedRegistryTypeQueryString,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
forkJoin(observableList).subscribe(res => {
|
||||||
|
if (res && res.length) {
|
||||||
|
res.forEach(item => {
|
||||||
|
arr = arr.concat(item);
|
||||||
|
});
|
||||||
|
this.registries = arr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.messageHandlerService.error(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSystemAllowlist() {
|
getSystemAllowlist() {
|
||||||
@ -171,6 +276,11 @@ export class ProjectPolicyConfigComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isSystemAdmin(): boolean {
|
||||||
|
let account = this.session.getCurrentUser();
|
||||||
|
return account != null && account.has_admin_role;
|
||||||
|
}
|
||||||
|
|
||||||
retrieve(state?: State): any {
|
retrieve(state?: State): any {
|
||||||
this.projectService.getProject(this.projectId).subscribe(
|
this.projectService.getProject(this.projectId).subscribe(
|
||||||
response => {
|
response => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export class Project {
|
export class Project {
|
||||||
project_id: number;
|
project_id: number;
|
||||||
owner_id?: number;
|
owner_id?: number;
|
||||||
|
registry_id?: number | null;
|
||||||
name: string;
|
name: string;
|
||||||
creation_time?: Date | string;
|
creation_time?: Date | string;
|
||||||
deleted?: number;
|
deleted?: number;
|
||||||
@ -21,6 +22,7 @@ export class Project {
|
|||||||
auto_scan: string | boolean;
|
auto_scan: string | boolean;
|
||||||
auto_sbom_generation: string | boolean;
|
auto_sbom_generation: string | boolean;
|
||||||
reuse_sys_cve_allowlist?: string;
|
reuse_sys_cve_allowlist?: string;
|
||||||
|
proxy_speed_kb?: number | null;
|
||||||
};
|
};
|
||||||
cve_allowlist?: object;
|
cve_allowlist?: object;
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -30,5 +32,6 @@ export class Project {
|
|||||||
this.metadata.severity = 'low';
|
this.metadata.severity = 'low';
|
||||||
this.metadata.auto_scan = false;
|
this.metadata.auto_scan = false;
|
||||||
this.metadata.auto_sbom_generation = false;
|
this.metadata.auto_sbom_generation = false;
|
||||||
|
this.metadata.proxy_speed_kb = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,6 +144,7 @@ export class ProjectDefaultService extends ProjectService {
|
|||||||
.put<any>(
|
.put<any>(
|
||||||
`${baseUrl}/${projectId}`,
|
`${baseUrl}/${projectId}`,
|
||||||
{
|
{
|
||||||
|
registry_id: projectPolicy.RegistryId,
|
||||||
metadata: {
|
metadata: {
|
||||||
public: projectPolicy.Public ? 'true' : 'false',
|
public: projectPolicy.Public ? 'true' : 'false',
|
||||||
enable_content_trust: projectPolicy.ContentTrust
|
enable_content_trust: projectPolicy.ContentTrust
|
||||||
@ -162,6 +163,7 @@ export class ProjectDefaultService extends ProjectService {
|
|||||||
? 'true'
|
? 'true'
|
||||||
: 'false',
|
: 'false',
|
||||||
reuse_sys_cve_allowlist: reuseSysCVEVAllowlist,
|
reuse_sys_cve_allowlist: reuseSysCVEVAllowlist,
|
||||||
|
proxy_speed_kb: projectPolicy.ProxySpeedKb.toString(),
|
||||||
},
|
},
|
||||||
cve_allowlist: projectAllowlist,
|
cve_allowlist: projectAllowlist,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user