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:
Lichao Xue 2024-10-17 14:59:28 +08:00 committed by GitHub
parent 4a5185995e
commit 5ac9733396
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 215 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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