mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-02 13:01:23 +01:00
Merge pull request #2850 from steven-zou/master
Enhance scanning status controlling
This commit is contained in:
commit
a047c1fe96
@ -2,7 +2,7 @@ export const REGISTRY_CONFIG_HTML: string = `
|
||||
<div>
|
||||
<replication-config #replicationConfig [(replicationConfig)]="config" [showSubTitle]="true"></replication-config>
|
||||
<system-settings #systemSettings [(systemSettings)]="config" [showSubTitle]="true" [hasAdminRole]="hasAdminRole" [hasCAFile]="hasCAFile"></system-settings>
|
||||
<vulnerability-config *ngIf="withClair" #vulnerabilityConfig [(vulnerabilityConfig)]="config" [showSubTitle]="true" [clairDBStatus]="clairDB"></vulnerability-config>
|
||||
<vulnerability-config *ngIf="withClair" #vulnerabilityConfig [(vulnerabilityConfig)]="config" [showSubTitle]="true"></vulnerability-config>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="shouldDisable">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="shouldDisable">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
|
@ -52,7 +52,8 @@ describe('RegistryConfigComponent (inline template)', () => {
|
||||
"project_creation_restriction": "everyone",
|
||||
"self_registration": true,
|
||||
"has_ca_root": true,
|
||||
"harbor_version": "v1.1.1-rc1-160-g565110d"
|
||||
"harbor_version": "v1.1.1-rc1-160-g565110d",
|
||||
"next_scan_all": 0
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
@ -85,7 +86,7 @@ describe('RegistryConfigComponent (inline template)', () => {
|
||||
systemInfoService = fixture.debugElement.injector.get(SystemInfoService);
|
||||
spy = spyOn(cfgService, 'getConfigurations').and.returnValue(Promise.resolve(mockConfig));
|
||||
saveSpy = spyOn(cfgService, 'saveConfigurations').and.returnValue(Promise.resolve(true));
|
||||
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo));
|
||||
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValue(Promise.resolve(mockSystemInfo));
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@ -54,17 +54,8 @@ export class RegistryConfigComponent implements OnInit {
|
||||
return this.systemInfo && this.systemInfo.with_clair;
|
||||
}
|
||||
|
||||
get clairDB(): ClairDBStatus {
|
||||
return this.systemInfo && this.systemInfo.clair_vulnerability_status ?
|
||||
this.systemInfo.clair_vulnerability_status : null;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
//Get system info
|
||||
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
|
||||
.then((info: SystemInfo) => this.systemInfo = info)
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
|
||||
this.loadSystemInfo();
|
||||
//Initialize
|
||||
this.load();
|
||||
}
|
||||
@ -82,20 +73,25 @@ export class RegistryConfigComponent implements OnInit {
|
||||
return !this._isEmptyObject(this.getChanges());
|
||||
}
|
||||
|
||||
//Get system info
|
||||
loadSystemInfo(): void {
|
||||
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
|
||||
.then((info: SystemInfo) => this.systemInfo = info)
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
//Load configurations
|
||||
load(): void {
|
||||
this.onGoing = true;
|
||||
toPromise<Configuration>(this.configService.getConfigurations())
|
||||
.then((config: Configuration) => {
|
||||
this.onGoing = false;
|
||||
|
||||
this.configCopy = this._clone(config);
|
||||
this.config = config;
|
||||
this.onGoing = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
|
||||
this.errorHandler.error(error);
|
||||
this.onGoing = false;
|
||||
});
|
||||
}
|
||||
|
||||
@ -118,6 +114,8 @@ export class RegistryConfigComponent implements OnInit {
|
||||
});
|
||||
//Reload to fetch all the updates
|
||||
this.load();
|
||||
//Reload all system info
|
||||
//this.loadSystemInfo();
|
||||
})
|
||||
.catch(error => {
|
||||
this.onGoing = false;
|
||||
|
@ -12,13 +12,13 @@ export const VULNERABILITY_CONFIG_HTML: string = `
|
||||
</clr-tooltip>
|
||||
<clr-dropdown *ngIf="isClairDBFullyReady" [clrMenuPosition]="'bottom-right'" style="margin-top:-8px;" class="clr-dropdown-override">
|
||||
<button class="btn btn-link btn-font" clrDropdownToggle>
|
||||
{{ updatedTimestamp }}
|
||||
{{ updatedTimestamp | date:'MM/dd/y HH:mm:ss' }}
|
||||
<clr-icon shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<div class="dropdown-menu" style="min-width:300px;">
|
||||
<div *ngFor="let nt of namespaceTimestamps" class="namespace">
|
||||
<span class="label label-info">{{nt.namespace}}</span>
|
||||
<span>{{convertToLocalTime(nt.last_update*1000)}}</span>
|
||||
<span>{{ convertToLocalTime(nt.last_update) | date:'MM/dd/y HH:mm:ss'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
@ -38,7 +38,8 @@ export const VULNERABILITY_CONFIG_HTML: string = `
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group form-group-override">
|
||||
<button class="btn btn-primary btn-sm" style="width:160px;" (click)="scanNow()">{{ 'CONFIG.SCANNING.SCAN_NOW' | translate }}</button>
|
||||
<button class="btn btn-primary btn-sm" style="width:160px;" (click)="scanNow()" [disabled]="!scanAvailable">{{ 'CONFIG.SCANNING.SCAN_NOW' | translate }}</button>
|
||||
<span style="margin-top: 12px;" *ngIf="!scanAvailable">{{ 'CONFIG.SCANNING.NEXT_SCAN' | translate }} {{ nextScanTimestamp | date:'HH:mm' }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
|
||||
import { Component, Input, Output, EventEmitter, ViewChild, OnInit } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { Configuration } from '../config';
|
||||
import { VULNERABILITY_CONFIG_HTML, VULNERABILITY_CONFIG_STYLES } from './vulnerability-config.component.template';
|
||||
import { ScanningResultService } from '../../service/scanning.service';
|
||||
import {
|
||||
ScanningResultService,
|
||||
SystemInfo,
|
||||
SystemInfoService
|
||||
} from '../../service/index';
|
||||
import { ErrorHandler } from '../../error-handler';
|
||||
import { toPromise } from '../../utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@ -19,9 +23,10 @@ const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
|
||||
template: VULNERABILITY_CONFIG_HTML,
|
||||
styles: [VULNERABILITY_CONFIG_STYLES, REGISTRY_CONFIG_STYLES]
|
||||
})
|
||||
export class VulnerabilityConfigComponent {
|
||||
export class VulnerabilityConfigComponent implements OnInit {
|
||||
_localTime: Date = new Date();
|
||||
|
||||
onSubmitting: boolean = false;
|
||||
config: Configuration;
|
||||
openState: boolean = false;
|
||||
@Output() configChange: EventEmitter<Configuration> = new EventEmitter<Configuration>();
|
||||
@ -46,21 +51,37 @@ export class VulnerabilityConfigComponent {
|
||||
}
|
||||
|
||||
@Input() showSubTitle: boolean = false;
|
||||
@Input() clairDBStatus: ClairDBStatus;
|
||||
systemInfo: SystemInfo;
|
||||
|
||||
get updatedTimestamp(): string {
|
||||
if (this.clairDBStatus && this.clairDBStatus.overall_last_update > 0) {
|
||||
return this.convertToLocalTime(this.clairDBStatus.overall_last_update*1000);
|
||||
get scanAvailable(): boolean {
|
||||
let dt: Date = new Date();
|
||||
return !this.onSubmitting && (this.nextScanTime <= 0 || dt.getTime() > ((this.nextScanTime + 30) * 1000));
|
||||
}
|
||||
|
||||
get nextScanTimestamp(): Date {
|
||||
return this.nextScanTime > 0 ? this.convertToLocalTime(this.nextScanTime) : null;
|
||||
}
|
||||
|
||||
get nextScanTime(): number {
|
||||
return this.systemInfo && this.systemInfo.next_scan_all ? this.systemInfo.next_scan_all : 0;
|
||||
}
|
||||
|
||||
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 "--";
|
||||
return null;
|
||||
}
|
||||
|
||||
get namespaceTimestamps(): ClairDetail[] {
|
||||
if (this.clairDBStatus &&
|
||||
this.clairDBStatus.details &&
|
||||
this.clairDBStatus.details.length > 0) {
|
||||
return this.clairDBStatus.details;
|
||||
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 [];
|
||||
@ -207,31 +228,57 @@ export class VulnerabilityConfigComponent {
|
||||
}
|
||||
|
||||
get isClairDBFullyReady(): boolean {
|
||||
return this.clairDBStatus && this.clairDBStatus.overall_last_update > 0;
|
||||
return this.systemInfo &&
|
||||
this.systemInfo.clair_vulnerability_status &&
|
||||
this.systemInfo.clair_vulnerability_status.overall_last_update > 0;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private scanningService: ScanningResultService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private translate: TranslateService) { }
|
||||
private translate: TranslateService,
|
||||
private systemInfoService: SystemInfoService
|
||||
) { }
|
||||
|
||||
convertToLocalTime(utcTime: number): string {
|
||||
let offset: number = this._localTime.getTimezoneOffset() * 60;
|
||||
let timeWithLocal: number = utcTime - offset;
|
||||
let dt = new Date();
|
||||
dt.setTime(timeWithLocal);
|
||||
return dt.toLocaleString();
|
||||
ngOnInit(): void {
|
||||
this.getSystemInfo();
|
||||
}
|
||||
|
||||
convertToLocalTime(utcTime: number): Date {
|
||||
let dt: Date = new Date();
|
||||
dt.setTime(utcTime * 1000);
|
||||
|
||||
return dt;
|
||||
}
|
||||
|
||||
scanNow(): void {
|
||||
if (this.onSubmitting) {
|
||||
return;//Aoid duplicated submitting
|
||||
}
|
||||
|
||||
this.onSubmitting = true;
|
||||
toPromise<any>(this.scanningService.startScanningAll())
|
||||
.then(() => {
|
||||
this.translate.get("CONFIG.SCANNING.TRIGGER_SCAN_ALL_SUCCESS").subscribe((res: string) => {
|
||||
this.errorHandler.info(res);
|
||||
});
|
||||
//TODO:
|
||||
//Change button disable status.
|
||||
|
||||
//Update system info
|
||||
this.getSystemInfo().then(() => {
|
||||
this.onSubmitting = false;
|
||||
}).catch(() => {
|
||||
this.onSubmitting = false;
|
||||
});
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error))
|
||||
.catch(error => {
|
||||
this.errorHandler.error(error);
|
||||
this.onSubmitting = false;
|
||||
});
|
||||
}
|
||||
|
||||
getSystemInfo(): Promise<SystemInfo> {
|
||||
return toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
|
||||
.then((info: SystemInfo) => this.systemInfo = info)
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
}
|
@ -18,7 +18,12 @@ import { ReplicationRule, ReplicationJob, Endpoint } from '../service/interface'
|
||||
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
|
||||
import {
|
||||
ReplicationService,
|
||||
ReplicationDefaultService,
|
||||
JobLogService,
|
||||
JobLogDefaultService
|
||||
} from '../service/index';
|
||||
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
|
||||
|
||||
@ -183,7 +188,8 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: ReplicationService, useClass: ReplicationDefaultService },
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService }
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService },
|
||||
{ provide: JobLogService, useClass: JobLogDefaultService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
@ -41,7 +41,9 @@ import {
|
||||
ScanningResultService,
|
||||
ScanningResultDefaultService,
|
||||
ConfigurationService,
|
||||
ConfigurationDefaultService
|
||||
ConfigurationDefaultService,
|
||||
JobLogService,
|
||||
JobLogDefaultService
|
||||
} from './service/index';
|
||||
import {
|
||||
ErrorHandler,
|
||||
@ -74,7 +76,8 @@ export const DefaultServiceConfig: IServiceConfig = {
|
||||
langMessagePathForHttpLoader: "i18n/langs/",
|
||||
langMessageFileSuffixForHttpLoader: "-lang.json",
|
||||
localI18nMessageVariableMap: {},
|
||||
configurationEndpoint: "/api/configurations"
|
||||
configurationEndpoint: "/api/configurations",
|
||||
scanJobEndpoint: "/api/jobs/scan"
|
||||
};
|
||||
|
||||
/**
|
||||
@ -112,7 +115,10 @@ export interface HarborModuleConfig {
|
||||
scanningService?: Provider,
|
||||
|
||||
//Service implementation for configuration
|
||||
configService?: Provider
|
||||
configService?: Provider,
|
||||
|
||||
//Service implementation for job log
|
||||
jobLogService?: Provider
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,6 +203,7 @@ export class HarborLibraryModule {
|
||||
config.tagService || { provide: TagService, useClass: TagDefaultService },
|
||||
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
|
||||
config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService },
|
||||
config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService },
|
||||
//Do initializing
|
||||
TranslateServiceInitializer,
|
||||
{
|
||||
@ -224,6 +231,7 @@ export class HarborLibraryModule {
|
||||
config.tagService || { provide: TagService, useClass: TagDefaultService },
|
||||
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
|
||||
config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService },
|
||||
config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService },
|
||||
ChannelService
|
||||
]
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplicationService, ReplicationDefaultService } from '../service/index';
|
||||
import { JobLogService, JobLogDefaultService } from '../service/index';
|
||||
|
||||
import { JobLogViewerComponent } from './job-log-viewer.component';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
@ -13,7 +13,7 @@ describe('JobLogViewerComponent (inline template)', () => {
|
||||
let component: JobLogViewerComponent;
|
||||
let fixture: ComponentFixture<JobLogViewerComponent>;
|
||||
let serviceConfig: IServiceConfig;
|
||||
let replicationService: ReplicationService;
|
||||
let jobLogService: JobLogDefaultService;
|
||||
let spy: jasmine.Spy;
|
||||
let testConfig: IServiceConfig = {
|
||||
replicationJobEndpoint: "/api/jobs/replication/testing"
|
||||
@ -29,7 +29,7 @@ describe('JobLogViewerComponent (inline template)', () => {
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: testConfig },
|
||||
{ provide: ReplicationService, useClass: ReplicationDefaultService }
|
||||
{ provide: JobLogService, useClass: JobLogDefaultService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
@ -39,9 +39,9 @@ describe('JobLogViewerComponent (inline template)', () => {
|
||||
component = fixture.componentInstance;
|
||||
|
||||
serviceConfig = TestBed.get(SERVICE_CONFIG);
|
||||
replicationService = fixture.debugElement.injector.get(ReplicationService);
|
||||
spy = spyOn(replicationService, 'getJobLog')
|
||||
.and.returnValues(Promise.resolve("job log text"));
|
||||
jobLogService = fixture.debugElement.injector.get(JobLogService);
|
||||
spy = spyOn(jobLogService, 'getJobLog')
|
||||
.and.returnValue(Promise.resolve("job log text"));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
export const JOB_LOG_VIEWER_TEMPLATE: string = `
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalSize]="'xl'">
|
||||
<h3 class="modal-title" class="log-viewer-title" style="margin-top: 0px;">{{'REPLICATION.JOB_LOG_VIEWER' | translate }}</h3>
|
||||
<h3 class="modal-title" class="log-viewer-title" style="margin-top: 0px;">{{title | translate }}</h3>
|
||||
<div class="modal-body">
|
||||
<div class="loading-back" [hidden]="!onGoing">
|
||||
<span class="spinner spinner-md"></span>
|
||||
|
@ -11,13 +11,15 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { JOB_LOG_VIEWER_TEMPLATE, JOB_LOG_VIEWER_STYLES } from './job-log-viewer.component.template';
|
||||
import { ReplicationService } from '../service/index';
|
||||
import { JobLogService } from '../service/index';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
import { toPromise } from '../utils';
|
||||
|
||||
const supportSet: string[] = ["replication", "scan"];
|
||||
|
||||
@Component({
|
||||
selector: 'job-log-viewer',
|
||||
template: JOB_LOG_VIEWER_TEMPLATE,
|
||||
@ -25,12 +27,32 @@ import { toPromise } from '../utils';
|
||||
})
|
||||
|
||||
export class JobLogViewerComponent {
|
||||
_jobType: string = "replication";
|
||||
|
||||
opened: boolean = false;
|
||||
log: string = '';
|
||||
onGoing: boolean = true;
|
||||
|
||||
@Input()
|
||||
get jobType(): string {
|
||||
return this._jobType;
|
||||
}
|
||||
set jobType(v: string) {
|
||||
if (supportSet.find((t: string) => t === v)) {
|
||||
this._jobType = v;
|
||||
}
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
if(this.jobType === "scan"){
|
||||
return "VULNERABILITY.JOB_LOG_VIEWER";
|
||||
}
|
||||
|
||||
return "REPLICATION.JOB_LOG_VIEWER";
|
||||
}
|
||||
|
||||
constructor(
|
||||
private replicationService: ReplicationService,
|
||||
private jobLogService: JobLogService,
|
||||
private errorHandler: ErrorHandler
|
||||
) { }
|
||||
|
||||
@ -47,7 +69,7 @@ export class JobLogViewerComponent {
|
||||
load(jobId: number | string): void {
|
||||
this.onGoing = true;
|
||||
|
||||
toPromise<string>(this.replicationService.getJobLog(jobId))
|
||||
toPromise<string>(this.jobLogService.getJobLog(this.jobType, jobId))
|
||||
.then((log: string) => {
|
||||
this.onGoing = false;
|
||||
this.log = log;
|
||||
|
@ -19,6 +19,7 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
|
||||
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
|
||||
import { JobLogService, JobLogDefaultService } from '../service/index';
|
||||
|
||||
describe('Replication Component (inline template)', ()=>{
|
||||
|
||||
@ -183,7 +184,8 @@ describe('Replication Component (inline template)', ()=>{
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: ReplicationService, useClass: ReplicationDefaultService },
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService }
|
||||
{ provide: EndpointService, useClass: EndpointDefaultService },
|
||||
{ provide: JobLogService, useClass: JobLogDefaultService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
@ -17,6 +17,7 @@ import { SystemInfoService, SystemInfoDefaultService } from '../service/system-i
|
||||
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
|
||||
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
|
||||
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
|
||||
import { click } from '../utils';
|
||||
|
||||
@ -101,7 +102,8 @@ describe('RepositoryComponentStackview (inline template)', () => {
|
||||
FilterComponent,
|
||||
VULNERABILITY_DIRECTIVES,
|
||||
PUSH_IMAGE_BUTTON_DIRECTIVES,
|
||||
INLINE_ALERT_DIRECTIVES
|
||||
INLINE_ALERT_DIRECTIVES,
|
||||
JobLogViewerComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
|
@ -180,4 +180,12 @@ export interface IServiceConfig {
|
||||
* @memberOf IServiceConfig
|
||||
*/
|
||||
configurationEndpoint?: string;
|
||||
|
||||
/**
|
||||
* The base endpoint of scan job service.
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IServiceConfig
|
||||
*/
|
||||
scanJobEndpoint?: string;
|
||||
}
|
@ -7,4 +7,5 @@ export * from './repository.service';
|
||||
export * from './tag.service';
|
||||
export * from './RequestQueryParams';
|
||||
export * from './scanning.service';
|
||||
export * from './configuration.service';
|
||||
export * from './configuration.service';
|
||||
export * from './job-log.service';
|
@ -168,6 +168,7 @@ export interface SystemInfo {
|
||||
has_ca_root?: boolean;
|
||||
harbor_version?: string;
|
||||
clair_vulnerability_status?: ClairDBStatus;
|
||||
next_scan_all?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
39
src/ui_ng/lib/src/service/job-log.service.spec.ts
Normal file
39
src/ui_ng/lib/src/service/job-log.service.spec.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { JobLogService, JobLogDefaultService } from './job-log.service';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
|
||||
describe('JobLogService', () => {
|
||||
const mockConfig: IServiceConfig = {
|
||||
replicationJobEndpoint: "/api/jobs/replication/testing",
|
||||
scanJobEndpoint: "/api/jobs/scan/testing"
|
||||
};
|
||||
|
||||
let config: IServiceConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
providers: [
|
||||
JobLogDefaultService,
|
||||
{
|
||||
provide: JobLogService,
|
||||
useClass: JobLogDefaultService
|
||||
}, {
|
||||
provide: SERVICE_CONFIG,
|
||||
useValue: mockConfig
|
||||
}]
|
||||
});
|
||||
|
||||
config = TestBed.get(SERVICE_CONFIG);
|
||||
});
|
||||
|
||||
it('should be initialized', inject([JobLogDefaultService], (service: JobLogService) => {
|
||||
expect(service).toBeTruthy();
|
||||
expect(config.replicationJobEndpoint).toEqual("/api/jobs/replication/testing");
|
||||
expect(config.scanJobEndpoint).toEqual("/api/jobs/scan/testing");
|
||||
}));
|
||||
});
|
84
src/ui_ng/lib/src/service/job-log.service.ts
Normal file
84
src/ui_ng/lib/src/service/job-log.service.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { RequestQueryParams } from './RequestQueryParams';
|
||||
import { ReplicationJob, ReplicationRule } from './interface';
|
||||
import { Injectable, Inject } from "@angular/core";
|
||||
import 'rxjs/add/observable/of';
|
||||
import { Http, RequestOptions } from '@angular/http';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from '../utils';
|
||||
|
||||
/**
|
||||
* Define the service methods to handle the job log related things.
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class JobLogService
|
||||
*/
|
||||
export abstract class JobLogService {
|
||||
|
||||
/**
|
||||
* Get the log of the specified job
|
||||
*
|
||||
* @abstract
|
||||
* @param {string} jobType
|
||||
* @param {(number | string)} jobId
|
||||
* @returns {(Observable<string> | Promise<string> | string)}
|
||||
* @memberof JobLogService
|
||||
*/
|
||||
abstract getJobLog(jobType: string, jobId: number | string): Observable<string> | Promise<string> | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement default service for job log service.
|
||||
*
|
||||
* @export
|
||||
* @class JobLogDefaultService
|
||||
* @extends {ReplicationService}
|
||||
*/
|
||||
@Injectable()
|
||||
export class JobLogDefaultService extends JobLogService {
|
||||
_replicationJobBaseUrl: string;
|
||||
_scanningJobBaseUrl: string;
|
||||
_supportedJobTypes: string[];
|
||||
|
||||
constructor(
|
||||
private http: Http,
|
||||
@Inject(SERVICE_CONFIG) config: IServiceConfig
|
||||
) {
|
||||
super();
|
||||
this._replicationJobBaseUrl = config.replicationJobEndpoint ?
|
||||
config.replicationJobEndpoint : '/api/jobs/replication';
|
||||
this._scanningJobBaseUrl = config.scanJobEndpoint ? config.scanJobEndpoint : "/api/jobs/scan";
|
||||
this._supportedJobTypes = ["replication", "scan"];
|
||||
}
|
||||
|
||||
_getJobLog(logUrl: string): Observable<string> | Promise<string> | string {
|
||||
return this.http.get(logUrl).toPromise()
|
||||
.then(response => response.text())
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
_isSupportedJobType(jobType: string): boolean {
|
||||
if (this._supportedJobTypes.find((t: string) => t === jobType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public getJobLog(jobType: string, jobId: number | string): Observable<string> | Promise<string> | string {
|
||||
if (!this._isSupportedJobType(jobType)) {
|
||||
return Promise.reject("Unsupport job type: " + jobType);
|
||||
}
|
||||
if (!jobId || jobId <= 0) {
|
||||
return Promise.reject('Bad argument');
|
||||
}
|
||||
|
||||
let logUrl: string = `${this._replicationJobBaseUrl}/${jobId}/log`;
|
||||
if (jobType === "scan") {
|
||||
logUrl = `${this._scanningJobBaseUrl}/${jobId}/log`;
|
||||
}
|
||||
|
||||
return this._getJobLog(logUrl);
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ export const TAG_STYLE = `
|
||||
|
||||
:host >>> .datagrid {
|
||||
margin: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
:host >>> .datagrid-placeholder {
|
||||
|
@ -17,6 +17,8 @@ import { FILTER_DIRECTIVES } from '../filter/index'
|
||||
import { Observable, Subscription } from 'rxjs/Rx';
|
||||
import { ChannelService } from '../channel/index';
|
||||
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
|
||||
describe('TagComponent (inline template)', () => {
|
||||
|
||||
let comp: TagComponent;
|
||||
@ -49,7 +51,8 @@ describe('TagComponent (inline template)', () => {
|
||||
TagComponent,
|
||||
ConfirmationDialogComponent,
|
||||
VULNERABILITY_DIRECTIVES,
|
||||
FILTER_DIRECTIVES
|
||||
FILTER_DIRECTIVES,
|
||||
JobLogViewerComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
|
@ -8,13 +8,16 @@ import {
|
||||
ScanningResultService,
|
||||
ScanningResultDefaultService,
|
||||
TagService,
|
||||
TagDefaultService
|
||||
TagDefaultService,
|
||||
JobLogService,
|
||||
JobLogDefaultService
|
||||
} from '../service/index';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { ErrorHandler } from '../error-handler/index';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { VULNERABILITY_SCAN_STATUS } from '../utils';
|
||||
import { ChannelService } from '../channel/index';
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
|
||||
describe('ResultBarChartComponent (inline template)', () => {
|
||||
let component: ResultBarChartComponent;
|
||||
@ -52,13 +55,15 @@ describe('ResultBarChartComponent (inline template)', () => {
|
||||
],
|
||||
declarations: [
|
||||
ResultBarChartComponent,
|
||||
ResultTipComponent],
|
||||
ResultTipComponent,
|
||||
JobLogViewerComponent],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
ChannelService,
|
||||
{ provide: SERVICE_CONFIG, useValue: testConfig },
|
||||
{ provide: TagService, useValue: TagDefaultService },
|
||||
{ provide: ScanningResultService, useValue: ScanningResultDefaultService }
|
||||
{ provide: ScanningResultService, useValue: ScanningResultDefaultService },
|
||||
{ provide: JobLogService, useValue: JobLogDefaultService}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,8 @@ import {
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef
|
||||
ChangeDetectorRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
import { SCANNING_STYLES } from './scanning.css';
|
||||
@ -21,6 +22,7 @@ import { ErrorHandler } from '../error-handler/index';
|
||||
import { toPromise } from '../utils';
|
||||
import { Observable, Subscription } from 'rxjs/Rx';
|
||||
import { ChannelService } from '../channel/index';
|
||||
import { JobLogViewerComponent } from '../job-log-viewer/index';
|
||||
|
||||
const STATE_CHECK_INTERVAL: number = 2000;//2s
|
||||
const RETRY_TIMES: number = 3;
|
||||
@ -39,6 +41,9 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
stateCheckTimer: Subscription;
|
||||
timerHandler: any;
|
||||
|
||||
@ViewChild("scanningLogViewer")
|
||||
scanningJobLogViewer: JobLogViewerComponent;
|
||||
|
||||
constructor(
|
||||
private tagService: TagService,
|
||||
private scanningService: ScanningResultService,
|
||||
@ -188,4 +193,11 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
|
||||
//Check error log
|
||||
viewLog(): void {
|
||||
if (this.summary && this.summary.job_id) {
|
||||
this.scanningJobLogViewer.open(this.summary.job_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ export const SCANNING_STYLES: string = `
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-left: -5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scanning-button {
|
||||
|
@ -96,8 +96,10 @@ export const BAR_CHART_COMPONENT_HTML: string = `
|
||||
<span class="label label-orange">{{'VULNERABILITY.STATE.QUEUED' | translate}}</span>
|
||||
</div>
|
||||
<div *ngIf="error" class="bar-state bar-state-error">
|
||||
<clr-icon shape="info-circle" class="is-error" size="24"></clr-icon>
|
||||
<span class="error-text">{{'VULNERABILITY.STATE.ERROR' | translate}}</span>
|
||||
<a href="javascript:void(0);" class="error-text" (click)="viewLog()">
|
||||
<clr-icon shape="error" class="is-error" size="24"></clr-icon>
|
||||
{{'VULNERABILITY.STATE.ERROR' | translate}}
|
||||
</a>
|
||||
</div>
|
||||
<div *ngIf="scanning" class="bar-state bar-state-chart">
|
||||
<div>{{'VULNERABILITY.STATE.SCANNING' | translate}}</div>
|
||||
@ -110,5 +112,6 @@ export const BAR_CHART_COMPONENT_HTML: string = `
|
||||
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
|
||||
<span style="margin-left:-5px;">{{'VULNERABILITY.STATE.UNKNOWN' | translate}}</span>
|
||||
</div>
|
||||
<job-log-viewer #scanningLogViewer [jobType]="'scan'"></job-log-viewer>
|
||||
</div>
|
||||
`;
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.9.8",
|
||||
"clarity-ui": "^0.9.8",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "0.3.24",
|
||||
"harbor-ui": "0.3.42",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -30,6 +30,7 @@ export class AppConfig {
|
||||
overall_last_update: 0,
|
||||
details: []
|
||||
};
|
||||
this.next_scan_all = 0;
|
||||
}
|
||||
|
||||
with_notary: boolean;
|
||||
@ -43,4 +44,5 @@ export class AppConfig {
|
||||
has_ca_root: boolean;
|
||||
harbor_version: string;
|
||||
clair_vulnerability_status?: ClairDBStatus;
|
||||
next_scan_all: number;
|
||||
}
|
@ -32,7 +32,7 @@
|
||||
<system-settings [(systemSettings)]="allConfig" [hasAdminRole]="hasAdminRole" [hasCAFile]="hasCAFile"></system-settings>
|
||||
</section>
|
||||
<section id="vulnerability" *ngIf="withClair" role="tabpanel" aria-labelledby="config-vulnerability" [hidden]='!isCurrentTabContent("vulnerability")'>
|
||||
<vulnerability-config [(vulnerabilityConfig)]="allConfig" [clairDBStatus]="clairDB"></vulnerability-config>
|
||||
<vulnerability-config [(vulnerabilityConfig)]="allConfig"></vulnerability-config>
|
||||
</section>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
|
@ -85,10 +85,6 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
return this.appConfigService.getConfig().with_clair;
|
||||
}
|
||||
|
||||
public get clairDB(): ClairDBStatus {
|
||||
return this.appConfigService.getConfig().clair_vulnerability_status;
|
||||
}
|
||||
|
||||
isCurrentTabLink(tabId: string): boolean {
|
||||
return this.currentTabId === tabId;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects', projectId, 'repositories']">< {{'REPOSITORY.REPOSITORIES' | translate}}</a>
|
||||
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">< {{'SEARCH.BACK' | translate}}</a>
|
||||
<hbr-tag (tagClickEvent)="watchTagClickEvt($event)" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="false"></hbr-tag>
|
||||
<hbr-tag (tagClickEvent)="watchTagClickEvt($event)" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="false" [withClair]="withClair"></hbr-tag>
|
||||
</div>
|
@ -29,8 +29,6 @@ export class TagRepositoryComponent implements OnInit {
|
||||
repoName: string;
|
||||
hasProjectAdminRole: boolean = false;
|
||||
registryUrl: string;
|
||||
withNotary: boolean;
|
||||
hasSignedIn: boolean;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@ -40,7 +38,6 @@ export class TagRepositoryComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.hasSignedIn = (this.session.getCurrentUser() !== null);
|
||||
let resolverData = this.route.snapshot.data;
|
||||
if (resolverData) {
|
||||
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||
@ -49,7 +46,18 @@ export class TagRepositoryComponent implements OnInit {
|
||||
this.repoName = this.route.snapshot.params['repo'];
|
||||
|
||||
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
||||
this.withNotary = this.appConfigService.getConfig().with_notary;
|
||||
}
|
||||
|
||||
get withNotary(): boolean {
|
||||
return this.appConfigService.getConfig().with_notary;
|
||||
}
|
||||
|
||||
get withClair(): boolean {
|
||||
return this.appConfigService.getConfig().with_clair;
|
||||
}
|
||||
|
||||
get hasSignedIn(): boolean {
|
||||
return this.session.getCurrentUser() !== null;
|
||||
}
|
||||
|
||||
watchTagClickEvt(tagEvt: TagClickEvent): void {
|
||||
|
@ -10,9 +10,8 @@
|
||||
<clr-dg-cell>{{p.creation_time | date: 'short'}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span>
|
||||
{{pagination.totalItems }} {{'PROJECT.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'PROJECT.OF' | translate}} </span> {{pagination.totalItems }} {{'PROJECT.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination>
|
||||
<!--{{(projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
|
||||
<clr-dg-pagination [clrDgPageSize]="15"></clr-dg-pagination>-->
|
||||
</clr-dg-footer>
|
||||
|
@ -8,8 +8,7 @@
|
||||
<clr-dg-cell>{{r.pull_count}}</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}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15"></clr-dg-pagination>
|
||||
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPOSITORY.OF' | translate}} </span> {{pagination.totalItems }} {{'REPOSITORY.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
@ -117,7 +117,8 @@
|
||||
"DELETION_TITLE": "Confirm user deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete user {{param}}?",
|
||||
"DELETE_SUCCESS": "User deleted successfully.",
|
||||
"ITEMS": "items"
|
||||
"ITEMS": "items",
|
||||
"OF": "of"
|
||||
},
|
||||
"PROJECT": {
|
||||
"PROJECTS": "Projects",
|
||||
@ -152,7 +153,8 @@
|
||||
"DELETED_SUCCESS": "Deleted project successfully.",
|
||||
"TOGGLED_SUCCESS": "Toggled project successfully.",
|
||||
"FAILED_TO_DELETE_PROJECT": "Project contains repositories or replication rules cannot be deleted.",
|
||||
"INLINE_HELP_PUBLIC": "When a project is set to public, anyone has read permission to the repositories under this project, and the user does not need to run \"docker login\" before pulling images under this project."
|
||||
"INLINE_HELP_PUBLIC": "When a project is set to public, anyone has read permission to the repositories under this project, and the user does not need to run \"docker login\" before pulling images under this project.",
|
||||
"OF": "of"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"REPOSITORIES": "Repositories",
|
||||
@ -182,7 +184,8 @@
|
||||
"DELETION_SUMMARY": "Do you want to delete project member {{param}}?",
|
||||
"ADDED_SUCCESS": "Added member successfully.",
|
||||
"DELETED_SUCCESS": "Deleted member successfully.",
|
||||
"SWITCHED_SUCCESS": "Switched member role successfully."
|
||||
"SWITCHED_SUCCESS": "Switched member role successfully.",
|
||||
"OF": "of"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Username",
|
||||
@ -201,7 +204,8 @@
|
||||
"SIMPLE": "Simple",
|
||||
"ITEMS": "items",
|
||||
"FILTER_PLACEHOLDER": "Filter Logs",
|
||||
"INVALID_DATE": "Invalid date."
|
||||
"INVALID_DATE": "Invalid date.",
|
||||
"OF": "of"
|
||||
},
|
||||
"REPLICATION": {
|
||||
"REPLICATION_RULE": "Replication Rule",
|
||||
@ -416,7 +420,8 @@
|
||||
"DAILY_POLICY": "Daily At",
|
||||
"REFRESH_POLICY": "Upon Refresh",
|
||||
"DB_REFRESH_TIME": "Database updated on",
|
||||
"DB_NOT_READY": "Vulnerability database might not be fully ready!"
|
||||
"DB_NOT_READY": "Vulnerability database might not be fully ready!",
|
||||
"NEXT_SCAN": "Available after"
|
||||
},
|
||||
"TEST_MAIL_SUCCESS": "Connection to mail server is verified.",
|
||||
"TEST_LDAP_SUCCESS": "Connection to LDAP server is verified.",
|
||||
@ -464,7 +469,7 @@
|
||||
"STATE": {
|
||||
"STOPPED": "Not Scanned",
|
||||
"QUEUED": "Queued",
|
||||
"ERROR": "Error",
|
||||
"ERROR": "View Log",
|
||||
"SCANNING": "Scanning",
|
||||
"UNKNOWN": "Unknown"
|
||||
},
|
||||
@ -496,7 +501,8 @@
|
||||
"PLACEHOLDER": "Filter Vulnerabilities",
|
||||
"PACKAGE": "Package with",
|
||||
"PACKAGES": "Packages with",
|
||||
"SCAN_NOW": "Scan"
|
||||
"SCAN_NOW": "Scan",
|
||||
"JOB_LOG_VIEWER": "View Scanning Job Log"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Image",
|
||||
|
@ -117,7 +117,8 @@
|
||||
"DELETION_TITLE": "Confirmar eliminación de usuario",
|
||||
"DELETION_SUMMARY": "¿Quiere eliminar el usuario {{param}}?",
|
||||
"DELETE_SUCCESS": "Usuario eliminado satisfactoriamente.",
|
||||
"ITEMS": "elementos"
|
||||
"ITEMS": "elementos",
|
||||
"OF": "of"
|
||||
},
|
||||
"PROJECT": {
|
||||
"PROJECTS": "Proyectos",
|
||||
@ -152,7 +153,8 @@
|
||||
"DELETED_SUCCESS": "Proyecto eliminado satisfactoriamente.",
|
||||
"TOGGLED_SUCCESS": "Proyecto alternado satisfactoriamente.",
|
||||
"FAILED_TO_DELETE_PROJECT": "Los proyectos que contienen repositorios o reglas de replicación no pueden eliminarse.",
|
||||
"INLINE_HELP_PUBLIC": "Cuando un proyecto se marca como público, todo el mundo tiene permisos de lectura sobre los repositorio de dicho proyecto, y no hace falta hacer \"docker login\" antes de subir imágenes a ellos."
|
||||
"INLINE_HELP_PUBLIC": "Cuando un proyecto se marca como público, todo el mundo tiene permisos de lectura sobre los repositorio de dicho proyecto, y no hace falta hacer \"docker login\" antes de subir imágenes a ellos.",
|
||||
"OF": "of"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"REPOSITORIES": "Repositorios",
|
||||
@ -182,7 +184,8 @@
|
||||
"DELETION_SUMMARY": "¿Quiere eliminar el miembro {{param}} del proyecto?",
|
||||
"ADDED_SUCCESS": "Miembro añadido satisfactoriamente.",
|
||||
"DELETED_SUCCESS": "Miembro eliminado satisfactoriamente",
|
||||
"SWITCHED_SUCCESS": "Rol del miembro cambiado satisfactoriamente."
|
||||
"SWITCHED_SUCCESS": "Rol del miembro cambiado satisfactoriamente.",
|
||||
"OF": "of"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Nombre de usuario",
|
||||
@ -201,7 +204,8 @@
|
||||
"SIMPLE": "Simple",
|
||||
"ITEMS": "elementos",
|
||||
"FILTER_PLACEHOLDER": "Filtrar logs",
|
||||
"INVALID_DATE": "Fecha invalida."
|
||||
"INVALID_DATE": "Fecha invalida.",
|
||||
"OF": "of"
|
||||
},
|
||||
"REPLICATION": {
|
||||
"REPLICATION_RULE": "Reglas de Replicación",
|
||||
@ -417,7 +421,8 @@
|
||||
"DAILY_POLICY": "Daily At",
|
||||
"REFRESH_POLICY": "Upon Refresh",
|
||||
"DB_REFRESH_TIME": "Database updated on",
|
||||
"DB_NOT_READY": "Vulnerability database might not be fully ready!"
|
||||
"DB_NOT_READY": "Vulnerability database might not be fully ready!",
|
||||
"NEXT_SCAN": "Available after"
|
||||
},
|
||||
"TEST_MAIL_SUCCESS": "La conexión al servidor de correo ha sido verificada.",
|
||||
"TEST_LDAP_SUCCESS": "La conexión al servidor LDAP ha sido verificada.",
|
||||
@ -463,7 +468,7 @@
|
||||
"STATE": {
|
||||
"STOPPED": "Not Scanned",
|
||||
"QUEUED": "Queued",
|
||||
"ERROR": "Error",
|
||||
"ERROR": "View Log",
|
||||
"SCANNING": "Scanning",
|
||||
"UNKNOWN": "Unknown"
|
||||
},
|
||||
@ -495,7 +500,8 @@
|
||||
"PLACEHOLDER": "Filter Vulnerabilities",
|
||||
"PACKAGE": "Package with",
|
||||
"PACKAGES": "Packages with",
|
||||
"SCAN_NOW": "Scan"
|
||||
"SCAN_NOW": "Scan",
|
||||
"JOB_LOG_VIEWER": "View Scanning Job Log"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "Push Image",
|
||||
|
@ -420,7 +420,8 @@
|
||||
"DAILY_POLICY": "每日定时",
|
||||
"REFRESH_POLICY": "缺陷库刷新后",
|
||||
"DB_REFRESH_TIME": "数据库更新于",
|
||||
"DB_NOT_READY": "缺陷数据库可能没有完全准备好!"
|
||||
"DB_NOT_READY": "缺陷数据库可能没有完全准备好!",
|
||||
"NEXT_SCAN": "下次可用时间"
|
||||
},
|
||||
"TEST_MAIL_SUCCESS": "邮件服务器的连通正常。",
|
||||
"TEST_LDAP_SUCCESS": "LDAP服务器的连通正常。",
|
||||
@ -468,7 +469,7 @@
|
||||
"STATE": {
|
||||
"STOPPED": "未扫描",
|
||||
"QUEUED": "已入队列",
|
||||
"ERROR": "错误",
|
||||
"ERROR": "查看日志",
|
||||
"SCANNING": "扫描中",
|
||||
"UNKNOWN": "未知"
|
||||
},
|
||||
@ -500,7 +501,8 @@
|
||||
"PLACEHOLDER": "过滤缺陷",
|
||||
"PACKAGE": "个组件有",
|
||||
"PACKAGES": "个组件有",
|
||||
"SCAN_NOW": "扫描"
|
||||
"SCAN_NOW": "扫描",
|
||||
"JOB_LOG_VIEWER": "查看扫描日志"
|
||||
},
|
||||
"PUSH_IMAGE": {
|
||||
"TITLE": "推送镜像",
|
||||
|
Loading…
Reference in New Issue
Block a user