mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 08:38:03 +01:00
Merge pull request #9611 from AllForNothing/scanner-bug
Improve scanner UI
This commit is contained in:
commit
5f62b5778b
@ -153,8 +153,6 @@ watchRepoClickEvent(repo: RepositoryItem): void {
|
||||
|
||||
**hasProjectAdminRole** is a user session related property to determined whether the current user has project administrator role. Some action menus might be disabled based on this property.
|
||||
|
||||
**withClair** is Clair installed
|
||||
|
||||
**withNotary** is Notary installed
|
||||
|
||||
**tagClickEvent** is an @output event emitter for you to catch the tag click events.
|
||||
@ -162,7 +160,7 @@ watchRepoClickEvent(repo: RepositoryItem): void {
|
||||
**goBackClickEvent** is an @output event emitter for you to catch the go back events.
|
||||
|
||||
```
|
||||
<hbr-repository [projectId]="" [repoName]="" [hasSignedIn]="" [hasProjectAdminRole]="" [withClair]="" [withNotary]=""
|
||||
<hbr-repository [projectId]="" [repoName]="" [hasSignedIn]="" [hasProjectAdminRole]="" [withNotary]=""
|
||||
(tagClickEvent)="watchTagClickEvt($event)" (backEvt)="watchGoBackEvt($event)" ></hbr-repository>
|
||||
|
||||
watchTagClickEvt(tagEvt: TagClickEvent): void {
|
||||
@ -213,7 +211,7 @@ watchAddInfoEvent(repo: RepositoryItem): void {
|
||||
**goBackClickEvent** is an @output event emitter for you to catch the go back events.
|
||||
|
||||
```
|
||||
<hbr-repository [projectId]="" [repoName]="" [hasSignedIn]="" [hasProjectAdminRole]="" [withClair]="" [withNotary]=""
|
||||
<hbr-repository [projectId]="" [repoName]="" [hasSignedIn]="" [hasProjectAdminRole]="" [withNotary]=""
|
||||
(tagClickEvent)="watchTagClickEvt($event)" (backEvt)="watchGoBackEvt($event)" ></hbr-repository>
|
||||
|
||||
watchTagClickEvt(tagEvt: TagClickEvent): void {
|
||||
|
@ -48,11 +48,6 @@ export class RegistryConfigComponent implements OnInit {
|
||||
get hasCAFile(): boolean {
|
||||
return this.systemInfo && this.systemInfo.has_ca_root;
|
||||
}
|
||||
|
||||
get withClair(): boolean {
|
||||
return this.systemInfo && this.systemInfo.with_clair;
|
||||
}
|
||||
|
||||
get withAdmiral(): boolean {
|
||||
return this.systemInfo && this.systemInfo.with_admiral;
|
||||
}
|
||||
|
@ -266,11 +266,6 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
.subscribe(systemInfo => this.systemInfo = systemInfo
|
||||
, error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
get withClair(): boolean {
|
||||
return this.systemInfo ? this.systemInfo.with_clair : false;
|
||||
}
|
||||
|
||||
getSystemWhitelist() {
|
||||
this.onGoing = true;
|
||||
this.systemInfoService.getSystemWhitelist()
|
||||
|
@ -1,28 +1,5 @@
|
||||
<form #systemConfigFrom="ngForm" class="compact">
|
||||
<section class="form-block">
|
||||
<label class="section-title" *ngIf="showSubTitle">{{ 'CONFIG.SCANNING.TITLE' | translate }}</label>
|
||||
<div>
|
||||
<label class="update-time">{{ 'CONFIG.SCANNING.DB_REFRESH_TIME' | translate }}</label>
|
||||
<clr-tooltip *ngIf="!isClairDBFullyReady">
|
||||
<clr-icon shape="warning" class="is-warning" size="22"></clr-icon>
|
||||
<clr-tooltip-content [clrPosition]="'top-right'" [clrSize]="'md'" *clrIfOpen>
|
||||
<span>{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
<clr-dropdown *ngIf="isClairDBFullyReady && showScanningNamespaces" class="clr-dropdown-override">
|
||||
<button class="btn btn-link btn-font" clrDropdownToggle>
|
||||
{{ updatedTimestamp | date:'short' }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu [clrPosition]="'bottom-right'" class="dropdown-namespace">
|
||||
<div *ngFor="let nt of namespaceTimestamps" class="namespace">
|
||||
<span class="label label-info">{{nt.namespace}}</span>
|
||||
<span>{{ convertToLocalTime(nt.last_update) | date:'short'}} </span>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<span *ngIf="isClairDBFullyReady && !showScanningNamespaces">{{ updatedTimestamp | date:'short' }} </span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<cron-selection #CronScheduleComponent [labelCurrent]="getLabelCurrent" [labelEdit]='getLabelCurrent' [originCron]='originCron' (inputvalue)="scanAll($event)"></cron-selection>
|
||||
<div class="btn-scan-right btn-scan">
|
||||
@ -30,4 +7,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</form>
|
||||
|
@ -62,28 +62,6 @@ export class VulnerabilityConfigComponent implements OnInit {
|
||||
this.getLabelCurrent = res;
|
||||
});
|
||||
}
|
||||
|
||||
get updatedTimestamp(): Date {
|
||||
if (this.systemInfo &&
|
||||
this.systemInfo.clair_vulnerability_status &&
|
||||
this.systemInfo.clair_vulnerability_status.overall_last_update > 0) {
|
||||
return this.convertToLocalTime(this.systemInfo.clair_vulnerability_status.overall_last_update);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get namespaceTimestamps(): ClairDetail[] {
|
||||
if (this.systemInfo &&
|
||||
this.systemInfo.clair_vulnerability_status &&
|
||||
this.systemInfo.clair_vulnerability_status.details &&
|
||||
this.systemInfo.clair_vulnerability_status.details.length > 0) {
|
||||
return this.systemInfo.clair_vulnerability_status.details;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
getSchedule() {
|
||||
this.onGoing = true;
|
||||
this.scanningService.getSchedule()
|
||||
@ -115,12 +93,6 @@ export class VulnerabilityConfigComponent implements OnInit {
|
||||
return this.systemSettingsForm && this.systemSettingsForm.valid;
|
||||
}
|
||||
|
||||
get isClairDBFullyReady(): boolean {
|
||||
return this.systemInfo &&
|
||||
this.systemInfo.clair_vulnerability_status &&
|
||||
this.systemInfo.clair_vulnerability_status.overall_last_update > 0;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getSystemInfo();
|
||||
this.getScanText();
|
||||
|
@ -56,7 +56,7 @@
|
||||
<clr-control-helper class="config-subtext"> {{ 'PROJECT_CONFIG.AUTOSCAN_POLICY' | translate }}
|
||||
</clr-control-helper>
|
||||
</clr-checkbox-container>
|
||||
<div class="clr-form-control" [class.clr-form-control-disabled]="!hasChangeConfigRole">
|
||||
<div *ngIf="systemInfo" class="clr-form-control" [class.clr-form-control-disabled]="!hasChangeConfigRole">
|
||||
<label for="systemWhitelist" class="clr-control-label">{{'CVE_WHITELIST.CVE_WHITELIST'|translate}}</label>
|
||||
<div class="w-100 clr-control-container">
|
||||
<div class="config-subtext">
|
||||
@ -134,10 +134,10 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="clr-col padding-top-8 ">
|
||||
<div class="clr-col padding-top-16 pl-2">
|
||||
<div class="clr-row expire-data">
|
||||
<label for="expires"
|
||||
class="bottom-line clr-col-4">{{'CVE_WHITELIST.EXPIRES_AT'|translate}}</label>
|
||||
class="bottom-line clr-col-3">{{'CVE_WHITELIST.EXPIRES_AT'|translate}}</label>
|
||||
<div class="underline">
|
||||
<input #dateSystemInput readonly type="date" [(clrDate)]="systemExpiresDate">
|
||||
<input [disabled]="!hasChangeConfigRole" *ngIf="!isUseSystemWhitelist()" #dateInput
|
||||
@ -149,7 +149,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<label for="expires" class="clr-col-4"></label>
|
||||
<label for="expires" class="clr-col-3"></label>
|
||||
<clr-checkbox-wrapper>
|
||||
<input [disabled]="isUseSystemWhitelist() || !hasChangeConfigRole"
|
||||
[checked]="neverExpires" [(ngModel)]="neverExpires" type="checkbox" clrCheckbox
|
||||
|
@ -42,8 +42,8 @@
|
||||
color: #0079bb;
|
||||
}
|
||||
|
||||
.padding-top-8 {
|
||||
padding-top: 8px;
|
||||
.padding-top-16 {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.position-relative {
|
||||
position: relative;
|
||||
|
@ -106,11 +106,9 @@ export class ProjectPolicyConfigComponent implements OnInit {
|
||||
this.systemInfoService.getSystemInfo()
|
||||
.subscribe(systemInfo => {
|
||||
this.systemInfo = systemInfo;
|
||||
if (this.withClair) {
|
||||
setTimeout(() => {
|
||||
this.dateSystemInput.nativeElement.parentNode.setAttribute("hidden", "hidden");
|
||||
}, 100);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.dateSystemInput.nativeElement.parentNode.setAttribute("hidden", "hidden");
|
||||
}, 100);
|
||||
} , error => this.errorHandler.error(error));
|
||||
// retrive project level policy data
|
||||
this.retrieve();
|
||||
@ -146,11 +144,6 @@ export class ProjectPolicyConfigComponent implements OnInit {
|
||||
public get withNotary(): boolean {
|
||||
return this.systemInfo ? this.systemInfo.with_notary : false;
|
||||
}
|
||||
|
||||
public get withClair(): boolean {
|
||||
return this.systemInfo ? this.systemInfo.with_clair : false;
|
||||
}
|
||||
|
||||
retrieve(state?: State): any {
|
||||
this.projectService.getProject(this.projectId)
|
||||
.subscribe(
|
||||
|
@ -39,10 +39,6 @@
|
||||
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="showDBStatusWarning" class="db-status-warning">
|
||||
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
|
||||
{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}
|
||||
</span>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
|
||||
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
|
||||
@ -105,4 +101,4 @@
|
||||
</ng-template>
|
||||
</hbr-gridview>
|
||||
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -104,11 +104,6 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
|
||||
public get registryUrl(): string {
|
||||
return this.systemInfo ? this.systemInfo.registry_url : "";
|
||||
}
|
||||
|
||||
public get withClair(): boolean {
|
||||
return this.systemInfo ? this.systemInfo.with_clair : false;
|
||||
}
|
||||
|
||||
public get isClairDBReady(): boolean {
|
||||
return (
|
||||
this.systemInfo &&
|
||||
@ -120,11 +115,6 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
|
||||
public get withAdmiral(): boolean {
|
||||
return this.mode === "admiral";
|
||||
}
|
||||
|
||||
public get showDBStatusWarning(): boolean {
|
||||
return this.withClair && !this.isClairDBReady;
|
||||
}
|
||||
|
||||
get canDownloadCert(): boolean {
|
||||
return this.systemInfo && this.systemInfo.has_ca_root;
|
||||
}
|
||||
|
@ -74,11 +74,6 @@ export class RepositoryComponent implements OnInit {
|
||||
public get withNotary(): boolean {
|
||||
return this.systemInfo ? this.systemInfo.with_notary : false;
|
||||
}
|
||||
|
||||
public get withClair(): boolean {
|
||||
return this.systemInfo ? this.systemInfo.with_clair : false;
|
||||
}
|
||||
|
||||
public get withAdmiral(): boolean {
|
||||
return this.systemInfo ? this.systemInfo.with_admiral : false;
|
||||
}
|
||||
|
@ -157,5 +157,12 @@ export const USERSTATICPERMISSION = {
|
||||
"READ": "read",
|
||||
}
|
||||
},
|
||||
"SCANNER": {
|
||||
"KEY": "scanner",
|
||||
"VALUE": {
|
||||
"READ": "read",
|
||||
"CREATE": "create"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -34,8 +34,6 @@ export class TagDetailComponent implements OnInit {
|
||||
repositoryId: string;
|
||||
@Input()
|
||||
withAdmiral: boolean;
|
||||
@Input()
|
||||
withClair: boolean;
|
||||
tagDetails: Tag = {
|
||||
name: "--",
|
||||
size: "--",
|
||||
@ -156,7 +154,8 @@ export class TagDetailComponent implements OnInit {
|
||||
}
|
||||
get hasCve(): boolean {
|
||||
return this.vulnerabilitySummary
|
||||
&& this.vulnerabilitySummary.scan_status === VULNERABILITY_SCAN_STATUS.SUCCESS;
|
||||
&& this.vulnerabilitySummary.scan_status === VULNERABILITY_SCAN_STATUS.SUCCESS
|
||||
&& this.vulnerabilitySummary.severity !== VULNERABILITY_SEVERITY.NONE;
|
||||
}
|
||||
public get scanCompletedDatetime(): Date {
|
||||
return this.tagDetails && this.tagDetails.scan_overview
|
||||
|
@ -92,44 +92,70 @@
|
||||
<clr-dg-placeholder>{{'TAG.PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-cell class="truncated flex-max-width">
|
||||
<a href="javascript:void(0)" (click)="onTagClick(t)" title="{{t.name}}">{{t.name}}</a>
|
||||
<span *ngIf="t.immutable" class="label label-info ml-1">{{'REPOSITORY.IMMUTABLE' | translate}}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{sizeTransform(t.size)}}</clr-dg-cell>
|
||||
<clr-dg-cell class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}">
|
||||
<hbr-copy-input #copyInput (onCopyError)="onCpError($event)" iconMode="true" defaultValue="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}"></hbr-copy-input>
|
||||
<div class="cell">
|
||||
<a href="javascript:void(0)" (click)="onTagClick(t)" title="{{t.name}}">{{t.name}}</a>
|
||||
<span *ngIf="t.immutable" class="label label-info ml-1">{{'REPOSITORY.IMMUTABLE' | translate}}</span>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<hbr-vulnerability-bar [repoName]="repoName" [tagId]="t.name" [summary]="handleScanOverview(t.scan_overview)"></hbr-vulnerability-bar>
|
||||
<div class="cell">
|
||||
{{sizeTransform(t.size)}}
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell class="truncated" title="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}">
|
||||
<div class="cell">
|
||||
<hbr-copy-input class="margin-top-m4" #copyInput (onCopyError)="onCpError($event)" iconMode="true" defaultValue="docker pull {{registryUrl}}/{{repoName}}:{{t.name}}"></hbr-copy-input>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="cell">
|
||||
<hbr-vulnerability-bar [repoName]="repoName" [tagId]="t.name" [summary]="handleScanOverview(t.scan_overview)"></hbr-vulnerability-bar>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="withNotary" [ngSwitch]="t.signature !== null">
|
||||
<clr-icon shape="check-circle" *ngSwitchCase="true" size="20" class="color-green"></clr-icon>
|
||||
<clr-icon shape="times-circle" *ngSwitchCase="false" size="16" class="color-red"></clr-icon>
|
||||
<a href="javascript:void(0)" *ngSwitchDefault role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="help" class="color-gray" size="16"></clr-icon>
|
||||
<span class="tooltip-content">{{'REPOSITORY.NOTARY_IS_UNDETERMINED' | translate}}</span>
|
||||
</a>
|
||||
<div class="cell">
|
||||
<clr-icon shape="check-circle" *ngSwitchCase="true" size="20" class="color-green"></clr-icon>
|
||||
<clr-icon shape="times-circle" *ngSwitchCase="false" size="16" class="color-red"></clr-icon>
|
||||
<a href="javascript:void(0)" *ngSwitchDefault role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="help" class="color-gray" size="16"></clr-icon>
|
||||
<span class="tooltip-content">{{'REPOSITORY.NOTARY_IS_UNDETERMINED' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell class="truncated" title="{{t.author}}">
|
||||
<div class="cell">
|
||||
{{t.author}}
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="cell">{{t.created | date: 'short'}}</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="cell">{{t.docker_version}}</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell class="truncated" title="{{t.author}}">{{t.author}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.created | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.docker_version}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="!withAdmiral">
|
||||
<hbr-label-piece *ngIf="t.labels?.length" [label]="t.labels[0]" [labelWidth]="90"> </hbr-label-piece>
|
||||
<div class="signpost-item" [hidden]="t.labels?.length<=1">
|
||||
<div class="trigger-item">
|
||||
<clr-signpost>
|
||||
<button class="btn btn-link" clrSignpostTrigger>...</button>
|
||||
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
|
||||
<div>
|
||||
<hbr-label-piece *ngFor="let label of t.labels" [label]="label"></hbr-label-piece>
|
||||
</div>
|
||||
</clr-signpost-content>
|
||||
</clr-signpost>
|
||||
<div class="cell">
|
||||
<hbr-label-piece *ngIf="t.labels?.length" [label]="t.labels[0]" [labelWidth]="90"> </hbr-label-piece>
|
||||
<div class="signpost-item" [hidden]="t.labels?.length<=1">
|
||||
<div class="trigger-item">
|
||||
<clr-signpost>
|
||||
<button class="btn btn-link" clrSignpostTrigger>...</button>
|
||||
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
|
||||
<div>
|
||||
<hbr-label-piece *ngFor="let label of t.labels" [label]="label"></hbr-label-piece>
|
||||
</div>
|
||||
</clr-signpost-content>
|
||||
</clr-signpost>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.push_time | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.pull_time | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="cell">{{t.push_time | date: 'short'}}</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="cell">{{t.pull_time | date: 'short'}}</div>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span> {{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
|
||||
|
@ -249,3 +249,13 @@ clr-datagrid {
|
||||
::ng-deep .clr-form-control {
|
||||
margin-top: 0;
|
||||
}
|
||||
.cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.margin-top-m4{
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
import { forkJoin, Observable, Subject, throwError as observableThrowError } from "rxjs";
|
||||
import { catchError, debounceTime, distinctUntilChanged, finalize, map } from 'rxjs/operators';
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { Comparator, Label, State, Tag, TagClickEvent } from "../service/interface";
|
||||
import { Comparator, Label, State, Tag, TagClickEvent, VulnerabilitySummary } from "../service/interface";
|
||||
|
||||
import {
|
||||
RequestQueryParams,
|
||||
@ -84,7 +84,6 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
@Input() isGuest: boolean;
|
||||
@Input() registryUrl: string;
|
||||
@Input() withNotary: boolean;
|
||||
@Input() withClair: boolean;
|
||||
@Input() withAdmiral: boolean;
|
||||
@Output() refreshRepo = new EventEmitter<boolean>();
|
||||
@Output() tagClickEvent = new EventEmitter<TagClickEvent>();
|
||||
@ -766,8 +765,9 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
this.scanningService.getProjectScanner(this.projectId)
|
||||
.subscribe(response => {
|
||||
if (response && "{}" !== JSON.stringify(response) && !response.disabled
|
||||
&& response.uuid) {
|
||||
this.getScannerMetadata(response.uuid);
|
||||
&& response.health === "healthy") {
|
||||
this.scanBtnState = ClrLoadingState.SUCCESS;
|
||||
this.hasEnabledScanner = true;
|
||||
} else {
|
||||
this.scanBtnState = ClrLoadingState.ERROR;
|
||||
}
|
||||
@ -775,16 +775,8 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
this.scanBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
}
|
||||
getScannerMetadata(uuid: string) {
|
||||
this.scanningService.getScannerMetadata(uuid)
|
||||
.subscribe(response => {
|
||||
this.hasEnabledScanner = true;
|
||||
this.scanBtnState = ClrLoadingState.SUCCESS;
|
||||
}, error => {
|
||||
this.scanBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
}
|
||||
handleScanOverview(scanOverview: any) {
|
||||
|
||||
handleScanOverview(scanOverview: any): VulnerabilitySummary {
|
||||
if (scanOverview) {
|
||||
return scanOverview[DEFAULT_SUPPORTED_MIME_TYPE];
|
||||
}
|
||||
|
@ -253,7 +253,8 @@ export const VULNERABILITY_SEVERITY = {
|
||||
LOW: "Low",
|
||||
MEDIUM: "Medium",
|
||||
HIGH: "High",
|
||||
CRITICAL: "Critical"
|
||||
CRITICAL: "Critical",
|
||||
NONE: "None"
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,7 @@
|
||||
<div>{{'VULNERABILITY.STATE.SCANNING' | translate}}</div>
|
||||
<div class="progress loop loop-height"><progress></progress></div>
|
||||
</div>
|
||||
<div *ngIf="completed" class="bar-state bar-state-chart">
|
||||
<div *ngIf="completed" class="bar-state bar-state-chart margin-top-m15">
|
||||
<hbr-result-tip-histogram [vulnerabilitySummary]="summary"></hbr-result-tip-histogram>
|
||||
</div>
|
||||
<div *ngIf="otherStatus" class="bar-state">
|
||||
|
@ -43,9 +43,6 @@ export class ResultGridComponent implements OnInit {
|
||||
this.channel.tagDetail$.subscribe(tag => {
|
||||
this.loadResults(this.repositoryId, this.tagId);
|
||||
});
|
||||
if (this.projectId) {
|
||||
this.getProjectScanner();
|
||||
}
|
||||
}
|
||||
getProjectScanner(): void {
|
||||
this.hasEnabledScanner = false;
|
||||
@ -53,8 +50,9 @@ export class ResultGridComponent implements OnInit {
|
||||
this.scanningService.getProjectScanner(this.projectId)
|
||||
.subscribe(response => {
|
||||
if (response && "{}" !== JSON.stringify(response) && !response.disabled
|
||||
&& response.uuid) {
|
||||
this.getScannerMetadata(response.uuid);
|
||||
&& response.health === "healthy") {
|
||||
this.scanBtnState = ClrLoadingState.SUCCESS;
|
||||
this.hasEnabledScanner = true;
|
||||
} else {
|
||||
this.scanBtnState = ClrLoadingState.ERROR;
|
||||
}
|
||||
@ -62,15 +60,6 @@ export class ResultGridComponent implements OnInit {
|
||||
this.scanBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
}
|
||||
getScannerMetadata(uuid: string) {
|
||||
this.scanningService.getScannerMetadata(uuid)
|
||||
.subscribe(response => {
|
||||
this.hasEnabledScanner = true;
|
||||
this.scanBtnState = ClrLoadingState.SUCCESS;
|
||||
}, error => {
|
||||
this.scanBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
}
|
||||
|
||||
loadResults(repositoryId: string, tagId: string): void {
|
||||
// only show loading for one time
|
||||
@ -138,6 +127,9 @@ export class ResultGridComponent implements OnInit {
|
||||
USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE);
|
||||
forkJoin(hasScanImagePermission).subscribe(permissions => {
|
||||
this.hasScanImagePermission = permissions[0] as boolean;
|
||||
if (this.projectId && this.hasScanImagePermission) {
|
||||
this.getProjectScanner();
|
||||
}
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
|
@ -1,14 +1,30 @@
|
||||
<div class="tip-wrapper tip-position width-210">
|
||||
<div class="tip-wrapper width-210">
|
||||
<clr-tooltip *ngIf="!isNone">
|
||||
<div clrTooltipTrigger class="level-border">
|
||||
<div [className]="getClass()">
|
||||
<div class="inner">
|
||||
{{vulnerabilitySummary?.severity | slice:0:1}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<clr-tooltip-content [clrPosition]="'left'" [clrSize]="'md'" *clrIfOpen>
|
||||
<span class="font-weight-600">{{'SCANNER.VULNERABILITY_SEVERITY' | translate }}</span>
|
||||
<span class="font-weight-600 margin-left-5">{{tipTitle}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
<clr-tooltip>
|
||||
<div clrTooltipTrigger class="tip-block">
|
||||
<div *ngIf="!isNone" class="circle-block">
|
||||
<div class="level-border" [className]="getClass()">{{vulnerabilitySummary?.severity | slice:0:1}}</div>
|
||||
<div class="black-point margin-left-5"></div>
|
||||
<span class="margin-left-5">{{total}}</span>
|
||||
<div class="black-point-container margin-left-10">
|
||||
<div class="black-point"></div>
|
||||
</div>
|
||||
<span class="margin-left-10 font-weight-800">{{total}}</span>
|
||||
<span class="margin-left-5">{{'SCANNER.TOTAL' | translate}}</span>
|
||||
<div class="black-point margin-left-10"></div>
|
||||
<span class="margin-left-5">{{fixableCount}}</span>
|
||||
<span class="margin-left-5">{{'SCANNER.FIXABLE' | translate}}</span>
|
||||
<div class="black-point-container margin-left-10">
|
||||
<div class="black-point "></div>
|
||||
</div>
|
||||
<span class="margin-left-10 font-weight-800 color-green">{{fixableCount}}</span>
|
||||
<span class="margin-left-5 color-green">{{'SCANNER.FIXABLE' | translate}}</span>
|
||||
</div>
|
||||
<div *ngIf="isNone" class="margin-left-5 tip-wrapper bar-block-none shadow-none width-150">{{'VULNERABILITY.NO_VULNERABILITY' | translate }}</div>
|
||||
</div>
|
||||
|
@ -1,3 +1,6 @@
|
||||
$thirty-pixel: 30px;
|
||||
$twenty-two-pixel: 22px;
|
||||
|
||||
.bar-wrapper {
|
||||
width: 144px;
|
||||
height: 12px;
|
||||
@ -40,21 +43,13 @@
|
||||
|
||||
.tip-wrapper {
|
||||
display: inline-block;
|
||||
height: 15px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.tip-position {
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.tip-block {
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
.bar-block-critical {
|
||||
background-color: red;
|
||||
}
|
||||
@ -102,7 +97,7 @@
|
||||
margin-bottom: 3px;
|
||||
span {
|
||||
:nth-child(1) {
|
||||
width: 30px;
|
||||
width: $thirty-pixel;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
@ -213,7 +208,7 @@ hr {
|
||||
}
|
||||
|
||||
.width-30 {
|
||||
width: 30px;
|
||||
width: $thirty-pixel;
|
||||
}
|
||||
|
||||
.width-210 {
|
||||
@ -225,51 +220,74 @@ hr {
|
||||
}
|
||||
.circle-block {
|
||||
color: #575757;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
div:first-child {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.level-border {
|
||||
border:1px solid #f8b5b4;
|
||||
.level-border>div{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
border-radius: 50%;
|
||||
height: $thirty-pixel;
|
||||
width: $thirty-pixel;
|
||||
line-height: $thirty-pixel;
|
||||
}
|
||||
|
||||
.inner {
|
||||
margin: auto;
|
||||
height: $twenty-two-pixel;
|
||||
width: $twenty-two-pixel;
|
||||
line-height: $twenty-two-pixel;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.level-critical {
|
||||
background:red;
|
||||
color:#621501;
|
||||
|
||||
color:red;
|
||||
}
|
||||
.level-high {
|
||||
background:#e64524;
|
||||
color:#621501;
|
||||
color:#e64524;
|
||||
}
|
||||
.level-medium {
|
||||
background-color: orange;
|
||||
color:#621501;
|
||||
color:orange;
|
||||
}
|
||||
.level-low {
|
||||
background: #007CBB;
|
||||
color:#cab6b1;
|
||||
color:#007CBB;
|
||||
}
|
||||
.level-negligible {
|
||||
background-color: green;
|
||||
color:#bad7ba;
|
||||
color:green;
|
||||
}
|
||||
.level-unknown {
|
||||
background-color: grey;
|
||||
color:#bad7ba;
|
||||
color:grey;
|
||||
}
|
||||
.black-point-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
height: 6px;
|
||||
}
|
||||
.black-point {
|
||||
display: inline-block;
|
||||
width: 4px;background-color: #000;
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
background-color: #000;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.font-weight-800 {
|
||||
font-weight: 800;
|
||||
}
|
||||
.color-green {
|
||||
color: green;
|
||||
}
|
||||
.tip-block {
|
||||
height: $thirty-pixel;
|
||||
line-height: $thirty-pixel;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export class ResultTipHistogramComponent implements OnInit {
|
||||
constructor(private translate: TranslateService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
let key = "VULNERABILITY.SEVERITY.UNKNOWN";
|
||||
let key = "VULNERABILITY.SEVERITY.NONE";
|
||||
switch (this.vulnerabilitySummary.severity) {
|
||||
case VULNERABILITY_SEVERITY.CRITICAL:
|
||||
key = "VULNERABILITY.SEVERITY.CRITICAL";
|
||||
@ -46,6 +46,9 @@ export class ResultTipHistogramComponent implements OnInit {
|
||||
case VULNERABILITY_SEVERITY.NEGLIGIBLE:
|
||||
key = "VULNERABILITY.SEVERITY.NEGLIGIBLE";
|
||||
break;
|
||||
case VULNERABILITY_SEVERITY.UNKNOWN:
|
||||
key = "VULNERABILITY.SEVERITY.UNKNOWN";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -41,12 +41,6 @@
|
||||
height: 10px;
|
||||
max-width: 120px;
|
||||
}
|
||||
.tip-position {
|
||||
margin-left: -4px;
|
||||
}
|
||||
.tip-block {
|
||||
margin-left: -3px;
|
||||
}
|
||||
.bar-block-high {
|
||||
background-color: #e64524;
|
||||
}
|
||||
@ -144,31 +138,29 @@ hr{
|
||||
.label-critical {
|
||||
background:red;
|
||||
color:#621501;
|
||||
border:1px solid #f8b5b4;
|
||||
}
|
||||
|
||||
|
||||
.label-danger {
|
||||
background:#e64524!important;
|
||||
color:#621501!important;
|
||||
border:1px solid #f8b5b4!important;
|
||||
}
|
||||
.label-medium {
|
||||
background-color: orange;
|
||||
color:#621501;
|
||||
border:1px solid #f8b5b4;
|
||||
}
|
||||
.label-low {
|
||||
background: #007CBB;
|
||||
color:#cab6b1;
|
||||
border:1px solid #f8b5b4;
|
||||
}
|
||||
.label-negligible {
|
||||
background-color: green;
|
||||
color:#bad7ba;
|
||||
border:1px solid #f8b5b4;
|
||||
}
|
||||
.label-unknown {
|
||||
background-color: grey;
|
||||
color:#bad7ba;
|
||||
border:1px solid #f8b5b4;
|
||||
}
|
||||
.margin-top-m15{
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
@ -111,11 +111,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
let account = this.session.getCurrentUser();
|
||||
return account != null;
|
||||
}
|
||||
|
||||
public get withClair(): boolean {
|
||||
return this.appConfigService.getConfig().with_clair;
|
||||
}
|
||||
|
||||
public get hasAdminRole(): boolean {
|
||||
return this.session.getCurrentUser() &&
|
||||
this.session.getCurrentUser().has_admin_role;
|
||||
|
@ -61,15 +61,15 @@
|
||||
<clr-dg-row *clrDgItems="let scanner of scanners" [clrDgItem]="scanner">
|
||||
<clr-dg-cell>
|
||||
<span>{{scanner.name}}</span>
|
||||
<span *ngIf="scanner.is_default" class="label label-info ml-1">{{'SCANNER.DEFAULT' | translate}}</span>
|
||||
<span *ngIf="scanner.is_default" class="label label-info ml-1 label-in-cell">{{'SCANNER.DEFAULT' | translate}}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{scanner.url}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<span *ngIf="scanner.loadingMetadata;else elseBlockLoading" class="spinner spinner-inline ml-2"></span>
|
||||
<ng-template #elseBlockLoading>
|
||||
<span *ngIf="scanner.metadata;else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||
<span *ngIf="scanner.metadata;else elseBlock" class="label label-success label-in-cell">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||
<ng-template #elseBlock>
|
||||
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
<span class="label label-danger label-in-cell">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</clr-dg-cell>
|
||||
|
@ -31,3 +31,7 @@
|
||||
.margin-left-10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.label-in-cell {
|
||||
position: absolute;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
@ -133,6 +133,7 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
||||
resetValue['description'] = this.selectedRow.description;
|
||||
resetValue['url'] = this.selectedRow.url;
|
||||
resetValue['skipCertVerify'] = this.selectedRow.skip_certVerify;
|
||||
resetValue['useInner'] = this.selectedRow.use_internal_addr;
|
||||
if (this.selectedRow.auth === 'Basic') {
|
||||
resetValue['auth'] = 'Basic';
|
||||
let username: string = this.selectedRow.access_credential.split(":")[0];
|
||||
|
@ -67,4 +67,9 @@ export class ConfigScannerService {
|
||||
return this.http.patch(`/api/scanners/${uid}`, {is_default: true} )
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
getProjectScanners(projectId: number) {
|
||||
return this.http.get(`/api/projects/${projectId}/scanner/candidates`)
|
||||
.pipe(map(response => response as Scanner[]))
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
}
|
||||
|
@ -105,19 +105,32 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label">{{"SCANNER.SKIP" | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
{{'SCANNER.SKIP_CERT_VERIFY' | translate}}
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<label class="clr-control-label">{{"SCANNER.OPTIONS" | translate}}</label>
|
||||
<div class="clr-control-container padding-top-3">
|
||||
<div class="clr-checkbox-wrapper">
|
||||
<input clrCheckbox formControlName="skipCertVerify"
|
||||
<clr-checkbox-wrapper>
|
||||
<input name="scanner-skipCertVerify" clrCheckbox formControlName="skipCertVerify"
|
||||
type="checkbox" id="scanner-skipCertVerify">
|
||||
</div>
|
||||
<label class="width-10rem" for="scanner-skipCertVerify">{{"SCANNER.SKIP" | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content class="width-14rem" clrPosition="top-left" clrSize="lg" *clrIfOpen>
|
||||
{{'SCANNER.SKIP_CERT_VERIFY' | translate}}
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
<clr-checkbox-wrapper>
|
||||
<input name="scanner-use-inner" clrCheckbox formControlName="useInner"
|
||||
type="checkbox" id="scanner-use-inner">
|
||||
<label class="width-10rem" for="scanner-use-inner">{{"SCANNER.USE_INNER" | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content class="width-14rem" clrPosition="top-left" clrSize="lg" *clrIfOpen>
|
||||
{{"SCANNER.USE_INNER_TIP" | translate}}
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -4,6 +4,10 @@
|
||||
.padding-top-3 {
|
||||
padding-top: 3px;
|
||||
}
|
||||
.clr-control-label {
|
||||
width: 9rem !important;
|
||||
|
||||
.width-10rem {
|
||||
width: 10rem;
|
||||
}
|
||||
.width-14rem {
|
||||
width: 14rem;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
|
||||
import { async, ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { NewScannerFormComponent } from "./new-scanner-form.component";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
@ -7,6 +7,7 @@ import { SharedModule } from "../../../shared/shared.module";
|
||||
import { ConfigScannerService } from "../config-scanner.service";
|
||||
import { of } from "rxjs";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { delay } from "rxjs/operators";
|
||||
|
||||
describe('NewScannerFormComponent', () => {
|
||||
let mockScanner1 = {
|
||||
@ -19,7 +20,10 @@ describe('NewScannerFormComponent', () => {
|
||||
let fixture: ComponentFixture<NewScannerFormComponent>;
|
||||
let fakedConfigScannerService = {
|
||||
getScannersByName() {
|
||||
return of([mockScanner1]);
|
||||
return of([mockScanner1]).pipe(delay(500));
|
||||
},
|
||||
getScannersByEndpointUrl() {
|
||||
return of([mockScanner1]).pipe(delay(500));
|
||||
}
|
||||
};
|
||||
beforeEach(async(() => {
|
||||
@ -57,32 +61,48 @@ describe('NewScannerFormComponent', () => {
|
||||
let el = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
|
||||
it('name should be valid', () => {
|
||||
it('name should be existed', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "test1";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
let el = null;
|
||||
setTimeout(() => {
|
||||
el = fixture.nativeElement.querySelector('#name-error');
|
||||
expect(el).toBeTruthy();
|
||||
}, 20000);
|
||||
tick(20000);
|
||||
}));
|
||||
it('name should be valid', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "test2";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
let el = null;
|
||||
setTimeout(() => {
|
||||
let el = fixture.nativeElement.querySelector('#name-error');
|
||||
el = fixture.nativeElement.querySelector('#name-error');
|
||||
expect(el).toBeFalsy();
|
||||
}, 11000);
|
||||
});
|
||||
}, 20000);
|
||||
tick(20000);
|
||||
}));
|
||||
|
||||
it('endpoint url should be valid', () => {
|
||||
it('endpoint url should be valid', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "test2";
|
||||
let urlInput = fixture.nativeElement.querySelector('#scanner-endpoint');
|
||||
urlInput.value = "http://168.0.0.1";
|
||||
urlInput.value = "http://168.0.0.2";
|
||||
urlInput.dispatchEvent(new Event('input'));
|
||||
urlInput.blur();
|
||||
urlInput.dispatchEvent(new Event('blur'));
|
||||
let el = null;
|
||||
setTimeout(() => {
|
||||
let el = fixture.nativeElement.querySelector('#endpoint-error');
|
||||
expect(el).toBeFalsy();
|
||||
}, 11000);
|
||||
});
|
||||
el = fixture.nativeElement.querySelector('#endpoint-error');
|
||||
}, 20000);
|
||||
tick(20000);
|
||||
expect(el).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('auth should be valid', () => {
|
||||
let authInput = fixture.nativeElement.querySelector('#scanner-authorization');
|
||||
|
@ -33,7 +33,8 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
||||
token: this.fb.control("", Validators.required),
|
||||
apiKey: this.fb.control("", Validators.required)
|
||||
}),
|
||||
skipCertVerify: this.fb.control(false)
|
||||
skipCertVerify: this.fb.control(false),
|
||||
useInner: this.fb.control(false)
|
||||
});
|
||||
checkNameSubscribe: any;
|
||||
checkEndpointUrlSubscribe: any;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
|
||||
import { async, ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { ClrLoadingState } from "@clr/angular";
|
||||
import { ConfigScannerService } from "../config-scanner.service";
|
||||
import { NewScannerModalComponent } from "./new-scanner-modal.component";
|
||||
@ -9,12 +9,13 @@ import { of, Subscription } from "rxjs";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { SharedModule } from "@harbor/ui";
|
||||
import { SharedModule as AppSharedModule } from "../../../shared/shared.module";
|
||||
import { Scanner } from "../scanner";
|
||||
|
||||
describe('NewScannerModalComponent', () => {
|
||||
let component: NewScannerModalComponent;
|
||||
let fixture: ComponentFixture<NewScannerModalComponent>;
|
||||
|
||||
let mockScanner1 = {
|
||||
let mockScanner1: Scanner = {
|
||||
name: 'test1',
|
||||
description: 'just a sample',
|
||||
url: 'http://168.0.0.1',
|
||||
@ -71,7 +72,7 @@ describe('NewScannerModalComponent', () => {
|
||||
let el = fixture.nativeElement.querySelector('#button-add');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
it('should be edit mode', () => {
|
||||
it('should be edit mode', fakeAsync(() => {
|
||||
component.isEdit = true;
|
||||
fixture.detectChanges();
|
||||
let el = fixture.nativeElement.querySelector('#button-save');
|
||||
@ -97,10 +98,11 @@ describe('NewScannerModalComponent', () => {
|
||||
el.click();
|
||||
el.dispatchEvent(new Event('click'));
|
||||
setTimeout(() => {
|
||||
expect(component.opened).toBeFalsy();
|
||||
}, 300);
|
||||
});
|
||||
it('test connection button should not be disabled', () => {
|
||||
expect(component.opened).toBeFalsy();
|
||||
}, 10000);
|
||||
tick(10000);
|
||||
}));
|
||||
it('test connection button should not be disabled', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "test2";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
@ -114,9 +116,10 @@ describe('NewScannerModalComponent', () => {
|
||||
expect(component.checkBtnState).toBe(ClrLoadingState.LOADING);
|
||||
setTimeout(() => {
|
||||
expect(component.checkBtnState).toBe(ClrLoadingState.SUCCESS);
|
||||
}, 300);
|
||||
});
|
||||
it('add button should not be disabled', () => {
|
||||
}, 10000);
|
||||
tick(10000);
|
||||
}));
|
||||
it('add button should not be disabled', fakeAsync(() => {
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = "test2";
|
||||
fixture.nativeElement.querySelector('#scanner-endpoint').value = "http://168.0.0.1";
|
||||
let authInput = fixture.nativeElement.querySelector('#scanner-authorization');
|
||||
@ -136,8 +139,9 @@ describe('NewScannerModalComponent', () => {
|
||||
el.dispatchEvent(new Event('click'));
|
||||
setTimeout(() => {
|
||||
expect(component.opened).toBeFalsy();
|
||||
}, 300);
|
||||
});
|
||||
}, 10000);
|
||||
tick(10000);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
@ -63,11 +63,12 @@ export class NewScannerModalComponent {
|
||||
scanner.access_credential = value.accessCredential.token;
|
||||
}
|
||||
scanner.skip_certVerify = !!value.skipCertVerify;
|
||||
scanner.use_internal_addr = !!value.useInner;
|
||||
this.configScannerService.addScanner(scanner)
|
||||
.pipe(finalize(() => this.onSaving = false))
|
||||
.subscribe(response => {
|
||||
this.close();
|
||||
this.msgHandler.showSuccess("ADD_SUCCESS");
|
||||
this.msgHandler.showSuccess("SCANNER.ADD_SUCCESS");
|
||||
this.notify.emit();
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
}, error => {
|
||||
@ -133,6 +134,9 @@ export class NewScannerModalComponent {
|
||||
if (this.originValue.skipCertVerify !== this.newScannerFormComponent.newScannerForm.get('skipCertVerify').value) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.useInner !== this.newScannerFormComponent.newScannerForm.get('useInner').value) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.auth === "Basic") {
|
||||
if (this.originValue.accessCredential.username !==
|
||||
this.newScannerFormComponent.newScannerForm.get('accessCredential').get('username').value) {
|
||||
@ -178,17 +182,18 @@ export class NewScannerModalComponent {
|
||||
scanner.access_credential = value.accessCredential.token;
|
||||
}
|
||||
scanner.skip_certVerify = !!value.skipCertVerify;
|
||||
scanner.use_internal_addr = !!value.useInner;
|
||||
this.configScannerService.testEndpointUrl(scanner)
|
||||
.pipe(finalize(() => this.onTesting = false))
|
||||
.subscribe(response => {
|
||||
this.inlineAlert.showInlineSuccess({
|
||||
message: "TEST_PASS"
|
||||
message: "SCANNER.TEST_PASS"
|
||||
});
|
||||
this.checkBtnState = ClrLoadingState.SUCCESS;
|
||||
this.testMap[this.newScannerFormComponent.newScannerForm.get('url').value] = true;
|
||||
}, error => {
|
||||
this.inlineAlert.showInlineError({
|
||||
message: "TEST_FAILED"
|
||||
message: "SCANNER.TEST_FAILED"
|
||||
});
|
||||
this.checkBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
@ -213,12 +218,13 @@ export class NewScannerModalComponent {
|
||||
this.editScanner.access_credential = value.accessCredential.token;
|
||||
}
|
||||
this.editScanner.skip_certVerify = !!value.skipCertVerify;
|
||||
this.editScanner.use_internal_addr = !!value.useInner;
|
||||
this.editScanner.uuid = this.uid;
|
||||
this.configScannerService.updateScanner(this.editScanner)
|
||||
.pipe(finalize(() => this.onSaving = false))
|
||||
.subscribe(response => {
|
||||
this.close();
|
||||
this.msgHandler.showSuccess("UPDATE_SUCCESS");
|
||||
this.msgHandler.showSuccess("SCANNER.UPDATE_SUCCESS");
|
||||
this.notify.emit();
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
}, error => {
|
||||
|
@ -7,16 +7,18 @@ export class Scanner {
|
||||
url?: string;
|
||||
auth?: string;
|
||||
access_credential?: string;
|
||||
scanner?: string;
|
||||
adapter?: string;
|
||||
disabled?: boolean;
|
||||
is_default?: boolean;
|
||||
skip_certVerify?: boolean;
|
||||
use_internal_addr?: boolean;
|
||||
create_time?: any;
|
||||
update_time?: any;
|
||||
vendor?: string;
|
||||
version?: string;
|
||||
metadata?: ScannerMetadata;
|
||||
loadingMetadata?: boolean;
|
||||
health?: string;
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
|
@ -302,8 +302,8 @@ const harborRoutes: Routes = [
|
||||
path: 'scanner',
|
||||
data: {
|
||||
permissionParam: {
|
||||
resource: USERSTATICPERMISSION.CONFIGURATION.KEY,
|
||||
action: USERSTATICPERMISSION.CONFIGURATION.VALUE.READ
|
||||
resource: USERSTATICPERMISSION.SCANNER.KEY,
|
||||
action: USERSTATICPERMISSION.SCANNER.VALUE.READ
|
||||
}
|
||||
},
|
||||
component: ScannerComponent
|
||||
|
@ -34,7 +34,7 @@
|
||||
<li class="nav-item" *ngIf="hasWebhookListPermission">
|
||||
<a class="nav-link" routerLink="webhook" routerLinkActive="active">{{'PROJECT_DETAIL.WEBHOOKS' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSessionValid && (hasConfigurationListPermission)">
|
||||
<li class="nav-item" *ngIf="hasScannerReadPermission">
|
||||
<a class="nav-link" routerLink="scanner" routerLinkActive="active">{{'SCANNER.SCANNER' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSessionValid && (hasConfigurationListPermission)">
|
||||
|
@ -45,6 +45,7 @@ export class ProjectDetailComponent implements OnInit {
|
||||
hasRobotListPermission: boolean;
|
||||
hasTagRetentionPermission: boolean;
|
||||
hasWebhookListPermission: boolean;
|
||||
hasScannerReadPermission: boolean;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@ -89,11 +90,14 @@ export class ProjectDetailComponent implements OnInit {
|
||||
USERSTATICPERMISSION.TAG_RETENTION.KEY, USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ));
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.WEBHOOK.KEY, USERSTATICPERMISSION.WEBHOOK.VALUE.LIST));
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.SCANNER.KEY, USERSTATICPERMISSION.SCANNER.VALUE.READ));
|
||||
|
||||
forkJoin(...permissionsList).subscribe(Rules => {
|
||||
[this.hasProjectReadPermission, this.hasLogListPermission, this.hasConfigurationListPermission, this.hasMemberListPermission
|
||||
, this.hasLabelListPermission, this.hasRepositoryListPermission, this.hasHelmChartsListPermission, this.hasRobotListPermission
|
||||
, this.hasLabelCreatePermission, this.hasTagRetentionPermission, this.hasWebhookListPermission] = Rules;
|
||||
, this.hasLabelCreatePermission, this.hasTagRetentionPermission, this.hasWebhookListPermission,
|
||||
this.hasScannerReadPermission] = Rules;
|
||||
}, error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,8 @@
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label name">{{'SCANNER.SCANNER' | translate}}</label>
|
||||
<div class="clr-control-container">
|
||||
|
||||
<label *ngIf="!(scanners && scanners.length > 0)" class="name">{{'SCANNER.NOT_AVAILABLE' | translate}}</label>
|
||||
<button *ngIf="(!scanner) && hasCreatePermission && scanners && scanners.length > 0" id="edit-scanner-copy" class="btn btn-primary btn-sm" (click)="open()">{{'SCANNER.EDIT' | translate}}</button>
|
||||
<label *ngIf="(!scanner) && hasCreatePermission && !(scanners && scanners.length > 0)" class="name">{{'SCANNER.NOT_AVAILABLE' | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="scanner">
|
||||
@ -16,15 +16,10 @@
|
||||
<div class="clr-input-wrapper">
|
||||
<div class="clr-input-wrapper">
|
||||
<span id="scanner-name" class="scanner-name">{{scanner?.name}}</span>
|
||||
<button *ngIf="scanners && scanners.length > 0" id="edit-scanner" class="btn btn-primary btn-sm" (click)="open()">{{'SCANNER.EDIT' | translate}}</button>
|
||||
<button *ngIf="hasCreatePermission && scanners && scanners.length > 0" id="edit-scanner" class="btn btn-primary btn-sm" (click)="open()">{{'SCANNER.EDIT' | translate}}</button>
|
||||
<span *ngIf="scanner?.disabled" class="label label-warning ml-1">{{'SCANNER.DISABLED' | translate}}</span>
|
||||
<span *ngIf="scanner?.loadingMetadata;else elseBlockLoading" class="spinner spinner-inline ml-2"></span>
|
||||
<ng-template #elseBlockLoading>
|
||||
<span *ngIf="scanner?.metadata;else elseBlock" class="label label-success ml-1">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||
<ng-template #elseBlock>
|
||||
<span class="label label-danger ml-1">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<span *ngIf="scanner?.health === 'unhealthy'" class="label label-danger ml-1">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
<span *ngIf="scanner?.health === 'healthy'" class="label label-success ml-1">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -38,29 +33,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control" *ngIf="scanner?.metadata?.scanner?.name">
|
||||
<div class="clr-form-control" *ngIf="scanner?.adapter">
|
||||
<label class="clr-control-label">{{'SCANNER.ADAPTER' | translate}}</label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<input [ngModel]="scanner?.metadata?.scanner?.name" readonly class="clr-input width-240" type="text" id="scanner-scanner"
|
||||
<input [ngModel]="scanner?.adapter" readonly class="clr-input width-240" type="text" id="scanner-scanner"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control" *ngIf="scanner?.metadata?.scanner?.vendor">
|
||||
<div class="clr-form-control" *ngIf="scanner?.vendor">
|
||||
<label class="clr-control-label">{{'SCANNER.VENDOR' | translate}}</label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<input [ngModel]="scanner?.metadata?.scanner?.vendor" readonly class="clr-input width-240" type="text" id="scanner-vendor"
|
||||
<input [ngModel]="scanner?.vendor" readonly class="clr-input width-240" type="text" id="scanner-vendor"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control" *ngIf="scanner?.metadata?.scanner?.version">
|
||||
<div class="clr-form-control" *ngIf="scanner?.version">
|
||||
<label class="clr-control-label">{{'SCANNER.VERSION' | translate}}</label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<input [ngModel]="scanner?.metadata?.scanner?.version" readonly class="clr-input width-240" type="text" id="scanner-version"
|
||||
<input [ngModel]="scanner?.version" readonly class="clr-input width-240" type="text" id="scanner-version"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
@ -74,26 +69,16 @@
|
||||
<clr-datagrid [(clrDgSingleSelected)]="selectedScanner">
|
||||
<clr-dg-column [clrDgField]="'name'">{{'SCANNER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'url'">{{'SCANNER.ENDPOINT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.HEALTH' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.DEFAULT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.AUTH' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let scanner of scanners" [clrDgItem]="scanner">
|
||||
<clr-dg-cell>{{scanner.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{scanner.url}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<span *ngIf="scanner.loadingMetadata;else elseBlockLoading" class="spinner spinner-inline ml-2">Loading...</span>
|
||||
<ng-template #elseBlockLoading>
|
||||
<span *ngIf="scanner.metadata;else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||
<ng-template #elseBlock>
|
||||
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<span *ngIf="scanner.is_default" class="label label-info">{{scanner.is_default}}</span>
|
||||
<span *ngIf="scanner.is_default" class="label label-info label-in-cell">{{scanner.is_default}}</span>
|
||||
<span *ngIf="!scanner.is_default">{{scanner.is_default}}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{scanner.auth?scanner.auth:('SCANNER.NONE'|translate)}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{scanner.description}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="scanners?.length > 0">1 - {{scanners?.length}} {{'WEBHOOK.OF' | translate}} </span> {{scanners?.length}} {{'WEBHOOK.ITEMS' | translate}}
|
||||
|
@ -21,3 +21,7 @@
|
||||
.clr-form-control {
|
||||
margin-top: 0.75rem !important;
|
||||
}
|
||||
.label-in-cell {
|
||||
position: absolute;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import { Component, OnInit, ViewChild } from "@angular/core";
|
||||
import { ConfigScannerService } from "../../config/scanner/config-scanner.service";
|
||||
import { Scanner } from "../../config/scanner/scanner";
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { ErrorHandler } from "@harbor/ui";
|
||||
import { ErrorHandler, UserPermissionService, USERSTATICPERMISSION } from "@harbor/ui";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { ClrLoadingState } from "@clr/angular";
|
||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
||||
@ -36,72 +36,57 @@ export class ScannerComponent implements OnInit {
|
||||
selectedScanner: Scanner;
|
||||
saveBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
onSaving: boolean = false;
|
||||
hasCreatePermission: boolean = false;
|
||||
@ViewChild(InlineAlertComponent, { static: false }) inlineAlert: InlineAlertComponent;
|
||||
constructor( private configScannerService: ConfigScannerService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private route: ActivatedRoute,
|
||||
private userPermissionService: UserPermissionService,
|
||||
) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
this.getPermission();
|
||||
this.init();
|
||||
}
|
||||
getPermission() {
|
||||
if (this.projectId) {
|
||||
this.userPermissionService.getPermission(this.projectId,
|
||||
USERSTATICPERMISSION.SCANNER.KEY, USERSTATICPERMISSION.SCANNER.VALUE.CREATE)
|
||||
.subscribe( permission => {
|
||||
this.hasCreatePermission = permission;
|
||||
if (this.hasCreatePermission) {
|
||||
this.getScanners();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
init() {
|
||||
this.getScanner();
|
||||
this.getScanners();
|
||||
}
|
||||
getScanner() {
|
||||
this.loading = true;
|
||||
this.configScannerService.getProjectScanner(this.projectId)
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(response => {
|
||||
if (response && "{}" !== JSON.stringify(response)) {
|
||||
this.scanner = response;
|
||||
this.getScannerMetadata();
|
||||
}
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
getScannerMetadata() {
|
||||
if (this.scanner && this.scanner.uuid) {
|
||||
this.scanner.loadingMetadata = true;
|
||||
this.configScannerService.getScannerMetadata(this.scanner.uuid)
|
||||
.pipe(finalize(() => this.scanner.loadingMetadata = false))
|
||||
.subscribe(response => {
|
||||
this.scanner.metadata = response;
|
||||
}, error => {
|
||||
this.scanner.metadata = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
getScanners() {
|
||||
this.loading = true;
|
||||
this.configScannerService.getScanners()
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(response => {
|
||||
if (response && response.length > 0) {
|
||||
this.scanners = response.filter(scanner => {
|
||||
return !scanner.disabled;
|
||||
});
|
||||
}
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
getMetadataForAll() {
|
||||
if (this.scanners && this.scanners.length > 0) {
|
||||
this.scanners.forEach((scanner, index) => {
|
||||
if (scanner.uuid ) {
|
||||
this.scanners[index].loadingMetadata = true;
|
||||
this.configScannerService.getScannerMetadata(scanner.uuid)
|
||||
.pipe(finalize(() => this.scanners[index].loadingMetadata = false))
|
||||
.subscribe(response => {
|
||||
this.scanners[index].metadata = response;
|
||||
}, error => {
|
||||
this.scanners[index].metadata = null;
|
||||
if (this.projectId) {
|
||||
this.configScannerService.getProjectScanners(this.projectId)
|
||||
.subscribe(response => {
|
||||
if (response && response.length > 0) {
|
||||
this.scanners = response.filter(scanner => {
|
||||
return !scanner.disabled;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
close() {
|
||||
@ -116,7 +101,6 @@ export class ScannerComponent implements OnInit {
|
||||
this.selectedScanner = s;
|
||||
}
|
||||
});
|
||||
this.getMetadataForAll();
|
||||
}
|
||||
get valid(): boolean {
|
||||
return this.selectedScanner
|
||||
|
@ -12,7 +12,7 @@
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-2 flex-150">
|
||||
<div class="dropdown" [ngClass]="{open:ruleIndex===i}">
|
||||
<button (click)="openEditor(i)" class="dropdown-toggle btn btn-link btn-sm">
|
||||
<button (click)="openEditor(i)" class="padding-left-0 dropdown-toggle btn btn-link btn-sm">
|
||||
{{'TAG_RETENTION.ACTION' | translate}}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
@ -57,10 +57,10 @@
|
||||
</ul>
|
||||
<div class="v-center clr-row" [ngClass]="{'pt-1':retention?.rules?.length > 0}">
|
||||
<div class="clr-col-2 flex-150"></div>
|
||||
<div class="clr-col-2">
|
||||
<div class="flex-8p">
|
||||
<button [disabled]="retention?.rules?.length >= 15" class="btn btn-primary btn-sm" (click)="openAddRule()">{{'TAG_RETENTION.ADD_RULE' | translate}}</button>
|
||||
</div>
|
||||
<div class="clr-col-8 color-97 font-size-54">
|
||||
<div class="clr-col-6 color-97 font-size-54">
|
||||
{{'TAG_RETENTION.ADD_RULE_HELP_1' | translate}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,4 +72,12 @@
|
||||
}
|
||||
.flex-150 {
|
||||
flex: 0 0 150px;
|
||||
max-width: 150px;
|
||||
}
|
||||
.padding-left-0 {
|
||||
padding-left: 0;
|
||||
}
|
||||
.flex-8p {
|
||||
flex: 0 0 8.3%;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
@ -44,10 +44,6 @@ export class TagDetailPageComponent implements OnInit {
|
||||
return this.appConfigService.getConfig().with_admiral;
|
||||
}
|
||||
|
||||
get withClair(): boolean {
|
||||
return this.appConfigService.getConfig().with_clair;
|
||||
}
|
||||
|
||||
goBack(tag: string): void {
|
||||
this.router.navigate(["harbor", "projects", this.projectId, "repositories", tag]);
|
||||
}
|
||||
|
@ -65,11 +65,6 @@ export class TagRepositoryComponent implements OnInit {
|
||||
get withNotary(): boolean {
|
||||
return this.appConfigService.getConfig().with_notary;
|
||||
}
|
||||
|
||||
get withClair(): boolean {
|
||||
return this.appConfigService.getConfig().with_clair;
|
||||
}
|
||||
|
||||
get withAdmiral(): boolean {
|
||||
return this.appConfigService.getConfig().with_admiral;
|
||||
}
|
||||
|
@ -284,3 +284,7 @@
|
||||
.text-xs-center {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.float-lg-right {
|
||||
float: right;
|
||||
}
|
||||
|
@ -951,7 +951,7 @@
|
||||
"SCANNING_TIME": "Scan completed time:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} of {{totalPackages}} {{package}} have known {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} of {{totalPackages}} {{package}} has known {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability package found"
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
},
|
||||
"SEVERITY": {
|
||||
"CRITICAL": "Critical",
|
||||
@ -1283,7 +1283,7 @@
|
||||
"TOKEN": "Token",
|
||||
"TOKEN_REQUIRED": "Token is required",
|
||||
"API_KEY_REQUIRED": "APIKey is required",
|
||||
"SKIP": "Skip Certificate Verification",
|
||||
"SKIP": "Skip certificate verification",
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
@ -1321,6 +1321,10 @@
|
||||
"DELETE_SUCCESS": "Successfully deleted",
|
||||
"TOTAL": "Total",
|
||||
"FIXABLE": "Fixable",
|
||||
"DURATION": "Duration:"
|
||||
"DURATION": "Duration:",
|
||||
"OPTIONS": "Options",
|
||||
"USE_INNER": "Use internal registry address",
|
||||
"USE_INNER_TIP": "If the option is checked, the scanner will be forced to use the internal registry address to access the related contents.",
|
||||
"VULNERABILITY_SEVERITY": "Vulnerability severity:"
|
||||
}
|
||||
}
|
||||
|
@ -950,7 +950,7 @@
|
||||
"SCANNING_TIME": "Scan completed time:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} of {{totalPackages}} {{package}} have known {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} of {{totalPackages}} {{package}} has known {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_ZERO": "No se encontró ningún paquete de vulnerabilidad reconocible"
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
},
|
||||
"SEVERITY": {
|
||||
"CRITICAL": "Critical",
|
||||
@ -1280,7 +1280,7 @@
|
||||
"TOKEN": "Token",
|
||||
"TOKEN_REQUIRED": "Token is required",
|
||||
"API_KEY_REQUIRED": "APIKey is required",
|
||||
"SKIP": "Skip Certificate Verification",
|
||||
"SKIP": "Skip certificate verification",
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
@ -1318,6 +1318,10 @@
|
||||
"DELETE_SUCCESS": "Successfully deleted",
|
||||
"TOTAL": "Total",
|
||||
"FIXABLE": "Fixable",
|
||||
"DURATION": "Duration:"
|
||||
"DURATION": "Duration:",
|
||||
"OPTIONS": "Options",
|
||||
"USE_INNER": "Use internal registry address",
|
||||
"USE_INNER_TIP": "If the option is checked, the scanner will be forced to use the internal registry address to access the related contents.",
|
||||
"VULNERABILITY_SEVERITY": "Vulnerability severity:"
|
||||
}
|
||||
}
|
||||
|
@ -924,7 +924,7 @@
|
||||
"SCANNING_TIME": "Temps d'analyse complète :",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} de {{totalPackages}} {{package}} ont des {{vulnerability}} connues.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} de {{totalPackages}} {{package}} a des {{vulnerability}} connues.",
|
||||
"TOOLTIPS_TITLE_ZERO": "Aucun paquet de vulnérabilité connue trouvé"
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
},
|
||||
"SEVERITY": {
|
||||
"CRITICAL": "Critique",
|
||||
@ -1252,7 +1252,7 @@
|
||||
"TOKEN": "Token",
|
||||
"TOKEN_REQUIRED": "Token is required",
|
||||
"API_KEY_REQUIRED": "APIKey is required",
|
||||
"SKIP": "Skip Certificate Verification",
|
||||
"SKIP": "Skip certificate verification",
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
@ -1290,6 +1290,10 @@
|
||||
"DELETE_SUCCESS": "Successfully deleted",
|
||||
"TOTAL": "Total",
|
||||
"FIXABLE": "Fixable",
|
||||
"DURATION": "Duration:"
|
||||
"DURATION": "Duration:",
|
||||
"OPTIONS": "Options",
|
||||
"USE_INNER": "Use internal registry address",
|
||||
"USE_INNER_TIP": "If the option is checked, the scanner will be forced to use the internal registry address to access the related contents.",
|
||||
"VULNERABILITY_SEVERITY": "Vulnerability severity:"
|
||||
}
|
||||
}
|
||||
|
@ -945,7 +945,7 @@
|
||||
"SCANNING_TIME": "Tempo de conclusão da análise:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} de {{totalPackages}} {{package}} possuem {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} de {{totalPackages}} {{package}} possuem {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_ZERO": "Nenhum pacote vulnerável reconhecido foi encontrado"
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
},
|
||||
"SEVERITY": {
|
||||
"CRITICAL": "Crítico",
|
||||
@ -1277,7 +1277,7 @@
|
||||
"TOKEN": "Token",
|
||||
"TOKEN_REQUIRED": "Token is required",
|
||||
"API_KEY_REQUIRED": "APIKey is required",
|
||||
"SKIP": "Skip Certificate Verification",
|
||||
"SKIP": "Skip certificate verification",
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
@ -1315,7 +1315,11 @@
|
||||
"DELETE_SUCCESS": "Successfully deleted",
|
||||
"TOTAL": "Total",
|
||||
"FIXABLE": "Fixable",
|
||||
"DURATION": "Duration:"
|
||||
"DURATION": "Duration:",
|
||||
"OPTIONS": "Options",
|
||||
"USE_INNER": "Use internal registry address",
|
||||
"USE_INNER_TIP": "If the option is checked, the scanner will be forced to use the internal registry address to access the related contents.",
|
||||
"VULNERABILITY_SEVERITY": "Vulnerability severity:"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -950,7 +950,7 @@
|
||||
"SCANNING_TIME": "Tarama tamamlanma zamanı:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} 'nın {{totalPackages}} {{package}} bilinen {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} 'nın {{totalPackages}} {{package}} bilinen {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_ZERO": "Tanınabilir bir güvenlik açığı paketi bulunamadı"
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
},
|
||||
"SEVERITY": {
|
||||
"CRITICAL": "Kritik",
|
||||
@ -1282,7 +1282,7 @@
|
||||
"TOKEN": "Token",
|
||||
"TOKEN_REQUIRED": "Token is required",
|
||||
"API_KEY_REQUIRED": "APIKey is required",
|
||||
"SKIP": "Skip Certificate Verification",
|
||||
"SKIP": "Skip certificate verification",
|
||||
"ADD_SCANNER": "Add Scanner",
|
||||
"EDIT_SCANNER": "Edit Scanner",
|
||||
"TEST_CONNECTION": "TEST CONNECTION",
|
||||
@ -1320,6 +1320,10 @@
|
||||
"DELETE_SUCCESS": "Successfully deleted",
|
||||
"TOTAL": "Total",
|
||||
"FIXABLE": "Fixable",
|
||||
"DURATION": "Duration:"
|
||||
"DURATION": "Duration:",
|
||||
"OPTIONS": "Options",
|
||||
"USE_INNER": "Use internal registry address",
|
||||
"USE_INNER_TIP": "If the option is checked, the scanner will be forced to use the internal registry address to access the related contents.",
|
||||
"VULNERABILITY_SEVERITY": "Vulnerability severity:"
|
||||
}
|
||||
}
|
||||
|
@ -950,7 +950,7 @@
|
||||
"SCANNING_TIME": "扫描完成时间:",
|
||||
"TOOLTIPS_TITLE": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}。",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}。",
|
||||
"TOOLTIPS_TITLE_ZERO": "没有发现可识别的漏洞包"
|
||||
"TOOLTIPS_TITLE_ZERO": "没有发现可识别的漏洞"
|
||||
},
|
||||
"SEVERITY": {
|
||||
"CRITICAL": "危急",
|
||||
@ -1267,7 +1267,7 @@
|
||||
"ENDPOINT_EXISTS": "地址已存在",
|
||||
"ENDPOINT_REQUIRED": "地址为必填项",
|
||||
"ILLEGAL_ENDPOINT": "非法地址",
|
||||
"AUTH": "Authorization",
|
||||
"AUTH": "认证模型",
|
||||
"NONE": "None",
|
||||
"BASIC": "Basic",
|
||||
"BEARER": "Bearer",
|
||||
@ -1317,6 +1317,10 @@
|
||||
"DELETE_SUCCESS": "删除成功",
|
||||
"TOTAL": "总计",
|
||||
"FIXABLE": "可修复",
|
||||
"DURATION": "扫描用时:"
|
||||
"DURATION": "扫描用时:",
|
||||
"OPTIONS": "选项",
|
||||
"USE_INNER": "使用仓库内部地址",
|
||||
"USE_INNER_TIP": "选中此项,扫描器将使用仓库内部地址访问其相关内容",
|
||||
"VULNERABILITY_SEVERITY": "漏洞严重度:"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user