mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 02:35:17 +01:00
Refactor gc and gi history page (#14728)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
2ffa6580fa
commit
705cb5b55d
@ -3,10 +3,7 @@ import { RouterModule, Routes } from "@angular/router";
|
||||
import { GcPageComponent } from "./gc-page.component";
|
||||
import { GcComponent } from "./gc/gc.component";
|
||||
import { GcHistoryComponent } from "./gc/gc-history/gc-history.component";
|
||||
import { GcRepoService } from "./gc/gc.service";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { GcApiDefaultRepository, GcApiRepository } from "./gc/gc.api.repository";
|
||||
import { GcViewModelFactory } from "./gc/gc.viewmodel.factory";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -24,10 +21,5 @@ const routes: Routes = [
|
||||
GcComponent,
|
||||
GcHistoryComponent
|
||||
],
|
||||
providers: [
|
||||
GcRepoService,
|
||||
{provide: GcApiRepository, useClass: GcApiDefaultRepository },
|
||||
GcViewModelFactory
|
||||
]
|
||||
})
|
||||
export class GcModule {}
|
||||
|
@ -1,30 +1,35 @@
|
||||
<h5 class="history-header" id="history-header">{{'GC.JOB_HISTORY' | translate}}</h5>
|
||||
<span class="refresh-btn" (click)="getJobs()">
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-column>{{'GC.JOB_ID' | translate}}</clr-dg-column>
|
||||
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="getJobs($event)">
|
||||
<clr-dg-column [clrDgField]="'id'">{{'GC.JOB_ID' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'GC.TRIGGER_TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'TAG_RETENTION.DRY_RUN' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'UPDATE_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="'update_time'">{{'UPDATE_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'LOGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let job of jobs" [clrDgItem]='job'>
|
||||
<clr-dg-row *ngFor="let job of jobs" [clrDgItem]='job'>
|
||||
<clr-dg-cell>{{job.id }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{(job.type ? 'SCHEDULE.'+ job.type.toUpperCase() : '') | translate }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{isDryRun(job?.parameters) | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.status.toUpperCase() | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.createTime | harborDatetime:'medium'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.updateTime | harborDatetime:'medium'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{(job.schedule?.type ? 'SCHEDULE.' + job.schedule?.type.toUpperCase() : '') | translate }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{isDryRun(job?.job_parameters) | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.job_status.toUpperCase() | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.creation_time | harborDatetime:'medium'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.update_time | harborDatetime:'medium'}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<a *ngIf="job.status.toLowerCase() === 'success' || job.status.toLowerCase() === 'error'" target="_blank" [href]="getLogLink(job.id)"><clr-icon shape="list"></clr-icon></a>
|
||||
<a *ngIf="job.job_status.toLowerCase() === 'success' || job.job_status.toLowerCase() === 'error'" target="_blank"
|
||||
[href]="getLogLink(job.id)">
|
||||
<clr-icon shape="list"></clr-icon>
|
||||
</a>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination [clrDgPageSize]="15">
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="page" [clrDgTotalItems]="total">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
{{'GC.LATEST_JOBS' | translate :{param: jobs.length} }}
|
||||
<span *ngIf="total">{{pagination.firstItem + 1}}
|
||||
- {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
|
||||
{{total}} {{'DESTINATION.ITEMS' | translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
@ -1,22 +1,22 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
ComponentFixtureAutoDetect,
|
||||
TestBed,
|
||||
waitForAsync
|
||||
ComponentFixtureAutoDetect, fakeAsync,
|
||||
TestBed, tick,
|
||||
} from '@angular/core/testing';
|
||||
import { GcRepoService } from "../gc.service";
|
||||
import { of } from 'rxjs';
|
||||
import { GcViewModelFactory } from "../gc.viewmodel.factory";
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { ErrorHandler } from '../../../../../shared/units/error-handler';
|
||||
import { GcHistoryComponent } from './gc-history.component';
|
||||
import { GcJobData } from "../gcLog";
|
||||
import { SharedTestingModule } from "../../../../../shared/shared.module";
|
||||
import { GCHistory } from "../../../../../../../ng-swagger-gen/models/gchistory";
|
||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||
import { Registry } from "../../../../../../../ng-swagger-gen/models/registry";
|
||||
import { GcService } from "../../../../../../../ng-swagger-gen/services/gc.service";
|
||||
import { CURRENT_BASE_HREF } from "../../../../../shared/units/utils";
|
||||
import { delay } from "rxjs/operators";
|
||||
|
||||
describe('GcHistoryComponent', () => {
|
||||
let component: GcHistoryComponent;
|
||||
let fixture: ComponentFixture<GcHistoryComponent>;
|
||||
const mockJobs: GcJobData[] = [
|
||||
const mockJobs: GCHistory[] = [
|
||||
{
|
||||
id: 1,
|
||||
job_name: 'test',
|
||||
@ -24,10 +24,8 @@ describe('GcHistoryComponent', () => {
|
||||
schedule: null,
|
||||
job_status: 'pending',
|
||||
job_parameters: '{"dry_run":true}',
|
||||
job_uuid: 'abc',
|
||||
creation_time: null,
|
||||
update_time: null,
|
||||
delete: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -36,41 +34,38 @@ describe('GcHistoryComponent', () => {
|
||||
schedule: null,
|
||||
job_status: 'finished',
|
||||
job_parameters: '{"dry_run":true}',
|
||||
job_uuid: 'bcd',
|
||||
creation_time: null,
|
||||
update_time: null,
|
||||
delete: false
|
||||
}
|
||||
];
|
||||
let fakeGcRepoService = {
|
||||
const fakedGcService = {
|
||||
count: 0,
|
||||
getJobs() {
|
||||
getGCHistoryResponse() {
|
||||
if (this.count === 0) {
|
||||
this.count += 1;
|
||||
return of([mockJobs[0]]);
|
||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
|
||||
headers: new HttpHeaders({'x-total-count': [mockJobs[0]].length.toString()}),
|
||||
body: [mockJobs[0]]
|
||||
});
|
||||
return of(response).pipe(delay(0));
|
||||
} else {
|
||||
this.count += 1;
|
||||
return of([mockJobs[1]]);
|
||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
|
||||
headers: new HttpHeaders({'x-total-count': [mockJobs[1]].length.toString()}),
|
||||
body: [mockJobs[1]]
|
||||
});
|
||||
return of(response).pipe(delay(0));
|
||||
}
|
||||
},
|
||||
getLogLink() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const fakeGcViewModelFactory = new GcViewModelFactory();
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GcHistoryComponent],
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
TranslateService,
|
||||
GcViewModelFactory,
|
||||
{ provide: GcRepoService, useValue: fakeGcRepoService },
|
||||
{ provide: GcViewModelFactory, useValue: fakeGcViewModelFactory },
|
||||
{provide: GcService, useValue: fakedGcService},
|
||||
// open auto detect
|
||||
{provide: ComponentFixtureAutoDetect, useValue: true}
|
||||
]
|
||||
@ -91,10 +86,14 @@ describe('GcHistoryComponent', () => {
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should retry getting jobs', waitForAsync(() => {
|
||||
it('should retry getting jobs', fakeAsync(() => {
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.jobs[1].status).toEqual('finished');
|
||||
expect(component.jobs[0].job_status).toEqual('finished');
|
||||
});
|
||||
}));
|
||||
it('should return right log link', () => {
|
||||
expect(component.getLogLink('1')).toEqual(`${CURRENT_BASE_HREF}/system/gc/1/log`);
|
||||
});
|
||||
});
|
||||
|
@ -1,54 +1,93 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { GcRepoService } from "../gc.service";
|
||||
import { GcJobViewModel } from "../gcLog";
|
||||
import { GcViewModelFactory } from "../gc.viewmodel.factory";
|
||||
import { ErrorHandler } from "../../../../../shared/units/error-handler";
|
||||
import { Subscription, timer } from "rxjs";
|
||||
import { REFRESH_TIME_DIFFERENCE } from '../../../../../shared/entities/shared.const';
|
||||
import { GcService } from "../../../../../../../ng-swagger-gen/services/gc.service";
|
||||
import { CURRENT_BASE_HREF, DEFAULT_PAGE_SIZE, getSortingString } from "../../../../../shared/units/utils";
|
||||
import { ClrDatagridStateInterface } from "@clr/angular";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { GCHistory } from "../../../../../../../ng-swagger-gen/models/gchistory";
|
||||
|
||||
const JOB_STATUS = {
|
||||
PENDING: "pending",
|
||||
RUNNING: "running"
|
||||
};
|
||||
const YES: string = 'TAG_RETENTION.YES';
|
||||
const NO: string = 'TAG_RETENTION.NO';
|
||||
|
||||
@Component({
|
||||
selector: 'gc-history',
|
||||
templateUrl: './gc-history.component.html',
|
||||
styleUrls: ['./gc-history.component.scss']
|
||||
})
|
||||
export class GcHistoryComponent implements OnInit, OnDestroy {
|
||||
jobs: Array<GcJobViewModel> = [];
|
||||
loading: boolean;
|
||||
jobs: Array<GCHistory> = [];
|
||||
loading: boolean = true;
|
||||
timerDelay: Subscription;
|
||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||
page: number = 1;
|
||||
total: number = 0;
|
||||
state: ClrDatagridStateInterface;
|
||||
constructor(
|
||||
private gcRepoService: GcRepoService,
|
||||
private gcViewModelFactory: GcViewModelFactory,
|
||||
private gcService: GcService,
|
||||
private errorHandler: ErrorHandler
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.page = 1;
|
||||
this.total = 0;
|
||||
this.getJobs();
|
||||
}
|
||||
|
||||
getJobs() {
|
||||
getJobs(state?: ClrDatagridStateInterface) {
|
||||
if (state) {
|
||||
this.state = state;
|
||||
}
|
||||
if (state && state.page) {
|
||||
this.pageSize = state.page.size;
|
||||
}
|
||||
let q: string;
|
||||
if (state && state.filters && state.filters.length) {
|
||||
q = encodeURIComponent(`${state.filters[0].property}=~${state.filters[0].value}`);
|
||||
}
|
||||
let sort: string;
|
||||
if (state && state.sort && state.sort.by) {
|
||||
sort = getSortingString(state);
|
||||
}
|
||||
this.loading = true;
|
||||
this.gcRepoService.getJobs().subscribe(jobs => {
|
||||
this.jobs = this.gcViewModelFactory.createJobViewModel(jobs);
|
||||
this.loading = false;
|
||||
this.gcService.getGCHistoryResponse({
|
||||
page: this.page,
|
||||
pageSize: this.pageSize,
|
||||
q: q,
|
||||
sort: sort
|
||||
}).pipe(finalize(() => this.loading = false))
|
||||
.subscribe(res => {
|
||||
// Get total count
|
||||
if (res.headers) {
|
||||
const xHeader: string = res.headers.get("X-Total-Count");
|
||||
if (xHeader) {
|
||||
this.total = parseInt(xHeader, 0);
|
||||
}
|
||||
this.jobs = res.body;
|
||||
}
|
||||
// to avoid some jobs not finished.
|
||||
if (!this.timerDelay) {
|
||||
this.timerDelay = timer(REFRESH_TIME_DIFFERENCE, REFRESH_TIME_DIFFERENCE).subscribe(() => {
|
||||
let count: number = 0;
|
||||
this.jobs.forEach(job => {
|
||||
if (
|
||||
job['status'] === JOB_STATUS.PENDING ||
|
||||
job['status'] === JOB_STATUS.RUNNING
|
||||
job.job_status === JOB_STATUS.PENDING ||
|
||||
job.job_status === JOB_STATUS.RUNNING
|
||||
) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
if (count > 0) {
|
||||
this.getJobs();
|
||||
this.getJobs(this.state);
|
||||
} else {
|
||||
this.timerDelay.unsubscribe();
|
||||
this.timerDelay = null;
|
||||
@ -78,7 +117,7 @@ export class GcHistoryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getLogLink(id): string {
|
||||
return this.gcRepoService.getLogLink(id);
|
||||
return `${CURRENT_BASE_HREF}/system/gc/${id}/log`;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,65 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { throwError as observableThrowError, Observable } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { CURRENT_BASE_HREF } from "../../../../shared/units/utils";
|
||||
|
||||
export abstract class GcApiRepository {
|
||||
abstract postSchedule(param): Observable<any>;
|
||||
|
||||
abstract putSchedule(param): Observable<any>;
|
||||
|
||||
abstract getSchedule(): Observable<any>;
|
||||
|
||||
abstract getLog(id): Observable<any>;
|
||||
|
||||
abstract getStatus(id): Observable<any>;
|
||||
|
||||
abstract getJobs(): Observable<any>;
|
||||
|
||||
abstract getLogLink(id): string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class GcApiDefaultRepository extends GcApiRepository {
|
||||
constructor(
|
||||
private http: HttpClient
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public postSchedule(param): Observable<any> {
|
||||
return this.http.post(`${CURRENT_BASE_HREF}/system/gc/schedule`, param)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public putSchedule(param): Observable<any> {
|
||||
return this.http.put(`${CURRENT_BASE_HREF}/system/gc/schedule`, param)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public getSchedule(): Observable<any> {
|
||||
return this.http.get(`${CURRENT_BASE_HREF}/system/gc/schedule`)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public getLog(id): Observable<any> {
|
||||
return this.http.get(`${CURRENT_BASE_HREF}/system/gc/${id}/log`)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public getStatus(id): Observable<any> {
|
||||
return this.http.get(`${CURRENT_BASE_HREF}/system/gc/${id}`)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public getJobs(): Observable<any> {
|
||||
return this.http.get(`${CURRENT_BASE_HREF}/system/gc`)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public getLogLink(id) {
|
||||
return `${CURRENT_BASE_HREF}/system/gc/${id}/log`;
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<div class="cron-selection">
|
||||
<cron-selection [labelCurrent]="getLabelCurrent" #CronScheduleComponent [labelEdit]='getLabelCurrent'
|
||||
[originCron]='originCron' (inputvalue)="scheduleGc($event)"></cron-selection>
|
||||
[originCron]='originCron' (inputvalue)="saveGcSchedule($event)"></cron-selection>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-2 flex-200"></div>
|
||||
@ -22,11 +22,11 @@
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-2 flex-200">
|
||||
<button class="btn btn-primary gc-start-btn" (click)="gcNow()"
|
||||
<button id="gc-now" class="btn btn-primary gc-start-btn" (click)="gcNow()"
|
||||
[disabled]="disableGC">{{'GC.GC_NOW' | translate}}</button>
|
||||
</div>
|
||||
<div class="clr-col">
|
||||
<button class="btn btn-outline gc-start-btn" (click)="dryRun()"
|
||||
<button id="gc-dry-run" class="btn btn-outline gc-start-btn" (click)="dryRun()"
|
||||
[disabled]="dryRunOnGoing">{{'TAG_RETENTION.WHAT_IF_RUN' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,35 +1,18 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { GcComponent } from './gc.component';
|
||||
import { GcApiRepository, GcApiDefaultRepository} from './gc.api.repository';
|
||||
import { GcRepoService } from './gc.service';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import { GcViewModelFactory } from './gc.viewmodel.factory';
|
||||
import { CronScheduleComponent } from '../../../../shared/components/cron-schedule';
|
||||
import { CronTooltipComponent } from "../../../../shared/components/cron-schedule";
|
||||
import { of } from 'rxjs';
|
||||
import { GcJobData } from './gcLog';
|
||||
import { CURRENT_BASE_HREF } from "../../../../shared/units/utils";
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { GcService } from "../../../../../../ng-swagger-gen/services/gc.service";
|
||||
import { ScheduleType } from "../../../../shared/entities/shared.const";
|
||||
|
||||
describe('GcComponent', () => {
|
||||
let component: GcComponent;
|
||||
let fixture: ComponentFixture<GcComponent>;
|
||||
let gcRepoService: GcRepoService;
|
||||
let gcRepoService: GcService;
|
||||
let mockSchedule = [];
|
||||
let mockJobs: GcJobData[] = [
|
||||
{
|
||||
id: 22222,
|
||||
schedule: null,
|
||||
job_status: 'string',
|
||||
job_parameters: '{"dry_run":true}',
|
||||
creation_time: new Date().toDateString(),
|
||||
update_time: new Date().toDateString(),
|
||||
job_name: 'string',
|
||||
job_kind: 'string',
|
||||
job_uuid: 'string',
|
||||
delete: false
|
||||
}
|
||||
];
|
||||
const fakedErrorHandler = {
|
||||
error(error) {
|
||||
return error;
|
||||
@ -39,7 +22,6 @@ describe('GcComponent', () => {
|
||||
}
|
||||
};
|
||||
let spySchedule: jasmine.Spy;
|
||||
let spyJobs: jasmine.Spy;
|
||||
let spyGcNow: jasmine.Spy;
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -48,10 +30,7 @@ describe('GcComponent', () => {
|
||||
],
|
||||
declarations: [ GcComponent, CronScheduleComponent, CronTooltipComponent],
|
||||
providers: [
|
||||
{ provide: GcApiRepository, useClass: GcApiDefaultRepository },
|
||||
{ provide: ErrorHandler, useValue: fakedErrorHandler },
|
||||
GcRepoService,
|
||||
GcViewModelFactory
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
@ -61,10 +40,9 @@ describe('GcComponent', () => {
|
||||
fixture = TestBed.createComponent(GcComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
gcRepoService = fixture.debugElement.injector.get(GcRepoService);
|
||||
spySchedule = spyOn(gcRepoService, "getSchedule").and.returnValues(of(mockSchedule));
|
||||
spyJobs = spyOn(gcRepoService, "getJobs").and.returnValues(of(mockJobs));
|
||||
spyGcNow = spyOn(gcRepoService, "manualGc").and.returnValues(of(true));
|
||||
gcRepoService = fixture.debugElement.injector.get(GcService);
|
||||
spySchedule = spyOn(gcRepoService, "getGCSchedule").and.returnValues(of(mockSchedule));
|
||||
spyGcNow = spyOn(gcRepoService, "createGCSchedule").and.returnValues(of(true));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('should create', () => {
|
||||
@ -72,12 +50,25 @@ describe('GcComponent', () => {
|
||||
});
|
||||
it('should get schedule and job', () => {
|
||||
expect(spySchedule.calls.count()).toEqual(1);
|
||||
expect(spyJobs.calls.count()).toEqual(1);
|
||||
});
|
||||
it('should trigger gcNow', () => {
|
||||
const ele: HTMLButtonElement = fixture.nativeElement.querySelector('.gc-start-btn');
|
||||
const ele: HTMLButtonElement = fixture.nativeElement.querySelector('#gc-now');
|
||||
ele.click();
|
||||
fixture.detectChanges();
|
||||
expect(spyGcNow.calls.count()).toEqual(1);
|
||||
});
|
||||
it('should trigger dry run', () => {
|
||||
const ele: HTMLButtonElement = fixture.nativeElement.querySelector('#gc-dry-run');
|
||||
ele.click();
|
||||
fixture.detectChanges();
|
||||
expect(spyGcNow.calls.count()).toEqual(1);
|
||||
});
|
||||
it('getScheduleType function should work', () => {
|
||||
expect(GcComponent.getScheduleType).toBeTruthy();
|
||||
expect(GcComponent.getScheduleType(null)).toEqual(ScheduleType.NONE);
|
||||
expect(GcComponent.getScheduleType('0 0 0 0 0 0')).toEqual(ScheduleType.CUSTOM);
|
||||
expect(GcComponent.getScheduleType('0 0 * * * *')).toEqual(ScheduleType.HOURLY);
|
||||
expect(GcComponent.getScheduleType('0 0 0 * * *')).toEqual(ScheduleType.DAILY);
|
||||
expect(GcComponent.getScheduleType('0 0 0 * * 0')).toEqual(ScheduleType.WEEKLY);
|
||||
});
|
||||
});
|
||||
|
@ -1,32 +1,26 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
OnInit
|
||||
} from "@angular/core";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { GcJobViewModel } from "./gcLog";
|
||||
import { GcViewModelFactory } from "./gc.viewmodel.factory";
|
||||
import { GcRepoService } from "./gc.service";
|
||||
import {
|
||||
SCHEDULE_TYPE_NONE,
|
||||
ONE_MINITUE,
|
||||
THREE_SECONDS, GCSchedule
|
||||
} from "./gc.const";
|
||||
import { ErrorHandler } from "../../../../shared/units/error-handler";
|
||||
import { CronScheduleComponent } from "../../../../shared/components/cron-schedule/cron-schedule.component";
|
||||
import { OriginCron } from '../../../../shared/services/interface';
|
||||
import { CronScheduleComponent } from "../../../../shared/components/cron-schedule";
|
||||
import { OriginCron } from '../../../../shared/services';
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { GcService } from "../../../../../../ng-swagger-gen/services/gc.service";
|
||||
import { GCHistory } from "../../../../../../ng-swagger-gen/models/gchistory";
|
||||
import { ScheduleType } from "../../../../shared/entities/shared.const";
|
||||
|
||||
const ONE_MINUTE = 60000;
|
||||
|
||||
@Component({
|
||||
selector: "gc-config",
|
||||
templateUrl: "./gc.component.html",
|
||||
styleUrls: ["./gc.component.scss"]
|
||||
})
|
||||
export class GcComponent implements OnInit {
|
||||
jobs: Array<GcJobViewModel> = [];
|
||||
schedule: GCSchedule = {};
|
||||
originCron: OriginCron;
|
||||
disableGC: boolean = false;
|
||||
getLabelCurrent = 'GC.CURRENT_SCHEDULE';
|
||||
@ -35,23 +29,20 @@ export class GcComponent implements OnInit {
|
||||
CronScheduleComponent: CronScheduleComponent;
|
||||
shouldDeleteUntagged: boolean;
|
||||
dryRunOnGoing: boolean = false;
|
||||
|
||||
constructor(
|
||||
private gcRepoService: GcRepoService,
|
||||
private gcViewModelFactory: GcViewModelFactory,
|
||||
private gcService: GcService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private translate: TranslateService
|
||||
) {
|
||||
translate.setDefaultLang("en-us");
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.getCurrentSchedule();
|
||||
this.getJobs();
|
||||
}
|
||||
|
||||
getCurrentSchedule() {
|
||||
this.loadingGcStatus.emit(true);
|
||||
this.gcRepoService.getSchedule()
|
||||
this.gcService.getGCSchedule()
|
||||
.pipe(finalize(() => {
|
||||
this.loadingGcStatus.emit(false);
|
||||
}))
|
||||
@ -62,40 +53,42 @@ export class GcComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
public initSchedule(schedule: GCSchedule) {
|
||||
if (schedule && schedule.schedule !== null) {
|
||||
this.schedule = schedule;
|
||||
this.originCron = this.schedule.schedule;
|
||||
private initSchedule(gcHistory: GCHistory) {
|
||||
if (gcHistory && gcHistory.schedule) {
|
||||
this.originCron = {
|
||||
type: gcHistory.schedule.type,
|
||||
cron: gcHistory.schedule.cron
|
||||
};
|
||||
} else {
|
||||
this.originCron = {
|
||||
type: SCHEDULE_TYPE_NONE,
|
||||
type: ScheduleType.NONE,
|
||||
cron: ''
|
||||
};
|
||||
}
|
||||
if (schedule && schedule.job_parameters) {
|
||||
this.shouldDeleteUntagged = JSON.parse(schedule.job_parameters).delete_untagged;
|
||||
if (gcHistory && gcHistory.job_parameters) {
|
||||
this.shouldDeleteUntagged = JSON.parse(gcHistory.job_parameters).delete_untagged;
|
||||
} else {
|
||||
this.shouldDeleteUntagged = false;
|
||||
}
|
||||
}
|
||||
|
||||
getJobs() {
|
||||
this.gcRepoService.getJobs().subscribe(jobs => {
|
||||
this.jobs = this.gcViewModelFactory.createJobViewModel(jobs);
|
||||
});
|
||||
}
|
||||
|
||||
gcNow(): void {
|
||||
this.disableGC = true;
|
||||
setTimeout(() => {
|
||||
this.enableGc();
|
||||
}, ONE_MINITUE);
|
||||
}, ONE_MINUTE);
|
||||
|
||||
this.gcRepoService.manualGc(this.shouldDeleteUntagged, false).subscribe(
|
||||
this.gcService.createGCSchedule({
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false
|
||||
},
|
||||
schedule: {
|
||||
type: ScheduleType.MANUAL
|
||||
}
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.translate.get("GC.MSG_SUCCESS").subscribe((res: string) => {
|
||||
this.errorHandler.info(res);
|
||||
});
|
||||
this.errorHandler.info("GC.MSG_SUCCESS");
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
@ -105,13 +98,19 @@ export class GcComponent implements OnInit {
|
||||
|
||||
dryRun() {
|
||||
this.dryRunOnGoing = true;
|
||||
this.gcRepoService.manualGc(this.shouldDeleteUntagged, true)
|
||||
this.gcService.createGCSchedule({
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: true
|
||||
},
|
||||
schedule: {
|
||||
type: ScheduleType.MANUAL
|
||||
}
|
||||
})
|
||||
.pipe(finalize(() => this.dryRunOnGoing = false))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.translate.get("GC.DRY_RUN_SUCCESS").subscribe((res: string) => {
|
||||
this.errorHandler.info(res);
|
||||
});
|
||||
this.errorHandler.info("GC.DRY_RUN_SUCCESS");
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
@ -123,44 +122,42 @@ export class GcComponent implements OnInit {
|
||||
this.disableGC = false;
|
||||
}
|
||||
|
||||
private resetSchedule(cron) {
|
||||
this.schedule = {
|
||||
saveGcSchedule(cron: string) {
|
||||
if (this.originCron && this.originCron.type !== ScheduleType.NONE) {// no schedule, then create
|
||||
this.gcService.createGCSchedule({
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false
|
||||
},
|
||||
schedule: {
|
||||
type: this.CronScheduleComponent.scheduleType,
|
||||
type: GcComponent.getScheduleType(cron),
|
||||
cron: cron
|
||||
}
|
||||
};
|
||||
if (!cron) {
|
||||
this.shouldDeleteUntagged = false;
|
||||
}
|
||||
this.getJobs();
|
||||
}
|
||||
|
||||
scheduleGc(cron: string) {
|
||||
let schedule = this.schedule;
|
||||
if (schedule && schedule.schedule && schedule.schedule.type !== SCHEDULE_TYPE_NONE) {
|
||||
this.gcRepoService.putScheduleGc(this.shouldDeleteUntagged, this.CronScheduleComponent.scheduleType, cron).subscribe(
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.translate
|
||||
.get("GC.MSG_SCHEDULE_RESET")
|
||||
.subscribe((res) => {
|
||||
this.errorHandler.info(res);
|
||||
this.errorHandler.info("GC.MSG_SCHEDULE_RESET");
|
||||
this.CronScheduleComponent.resetSchedule();
|
||||
});
|
||||
this.resetSchedule(cron);
|
||||
this.getCurrentSchedule(); // refresh schedule
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.gcRepoService.postScheduleGc(this.shouldDeleteUntagged, this.CronScheduleComponent.scheduleType, cron).subscribe(
|
||||
this.gcService.updateGCSchedule({
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false
|
||||
},
|
||||
schedule: {
|
||||
type: GcComponent.getScheduleType(cron),
|
||||
cron: cron
|
||||
}
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.translate.get("GC.MSG_SCHEDULE_SET").subscribe((res) => {
|
||||
this.errorHandler.info(res);
|
||||
this.errorHandler.info("GC.MSG_SCHEDULE_RESET");
|
||||
this.CronScheduleComponent.resetSchedule();
|
||||
});
|
||||
this.resetSchedule(cron);
|
||||
this.getCurrentSchedule(); // refresh schedule
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
@ -168,4 +165,20 @@ export class GcComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static getScheduleType(cron: string): 'Hourly' | 'Daily' | 'Weekly' | 'Custom' | 'Manual' | 'None' {
|
||||
if (cron) {
|
||||
if (cron === '0 0 * * * *') {
|
||||
return ScheduleType.HOURLY;
|
||||
}
|
||||
if (cron === '0 0 0 * * *') {
|
||||
return ScheduleType.DAILY;
|
||||
}
|
||||
if (cron === '0 0 0 * * 0') {
|
||||
return ScheduleType.WEEKLY;
|
||||
}
|
||||
return ScheduleType.CUSTOM;
|
||||
}
|
||||
return ScheduleType.NONE;
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { OriginCron } from "../../../../shared/services";
|
||||
|
||||
|
||||
export const SCHEDULE_TYPE_NONE = "None";
|
||||
|
||||
export const ONE_MINITUE = 60000;
|
||||
export const THREE_SECONDS = 3000;
|
||||
|
||||
export interface GCSchedule {
|
||||
schedule?: OriginCron;
|
||||
parameters?: {[key: string]: any};
|
||||
id?: number;
|
||||
job_name?: string;
|
||||
job_kind?: string;
|
||||
job_parameters?: string;
|
||||
job_status?: string;
|
||||
deleted?: boolean;
|
||||
creation_time?: Date;
|
||||
update_time?: Date;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,72 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subscription, Subject, of } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { GcApiRepository } from './gc.api.repository';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import { GcJobData } from './gcLog';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class GcRepoService {
|
||||
|
||||
constructor(
|
||||
private gcApiRepository: GcApiRepository,
|
||||
) {}
|
||||
|
||||
public manualGc(shouldDeleteUntagged: boolean, isDryRun: boolean): Observable<any> {
|
||||
const param = {
|
||||
"schedule": {
|
||||
"type": "Manual"
|
||||
},
|
||||
parameters: {
|
||||
delete_untagged: shouldDeleteUntagged,
|
||||
dry_run: isDryRun
|
||||
}
|
||||
};
|
||||
return this.gcApiRepository.postSchedule(param);
|
||||
}
|
||||
|
||||
public getJobs(): Observable <GcJobData []> {
|
||||
return this.gcApiRepository.getJobs();
|
||||
}
|
||||
|
||||
public getLog(id): Observable <any> {
|
||||
return this.gcApiRepository.getLog(id);
|
||||
}
|
||||
|
||||
public getSchedule(): Observable <any> {
|
||||
return this.gcApiRepository.getSchedule();
|
||||
}
|
||||
|
||||
public postScheduleGc(shouldDeleteUntagged: boolean, type, cron): Observable <any> {
|
||||
let param = {
|
||||
"schedule": {
|
||||
"type": type,
|
||||
"cron": cron,
|
||||
},
|
||||
parameters: {
|
||||
delete_untagged: shouldDeleteUntagged
|
||||
}
|
||||
};
|
||||
|
||||
return this.gcApiRepository.postSchedule(param);
|
||||
}
|
||||
|
||||
public putScheduleGc(shouldDeleteUntagged, type, cron): Observable <any> {
|
||||
let param = {
|
||||
"schedule": {
|
||||
"type": type,
|
||||
"cron": cron,
|
||||
},
|
||||
parameters: {
|
||||
delete_untagged: shouldDeleteUntagged
|
||||
}
|
||||
};
|
||||
|
||||
return this.gcApiRepository.putSchedule(param);
|
||||
}
|
||||
|
||||
public getLogLink(id): string {
|
||||
return this.gcApiRepository.getLogLink(id);
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GcJobData, GcJobViewModel } from './gcLog';
|
||||
|
||||
@Injectable()
|
||||
export class GcViewModelFactory {
|
||||
public createJobViewModel(jobs: GcJobData[]): GcJobViewModel[] {
|
||||
let gcViewModels: GcJobViewModel[] = [];
|
||||
for (let job of jobs) {
|
||||
|
||||
let createTime = new Date(job.creation_time);
|
||||
let updateTime = new Date(job.update_time);
|
||||
gcViewModels.push({
|
||||
id: job.id,
|
||||
type: job.schedule ? job.schedule.type : null,
|
||||
status: job.job_status,
|
||||
parameters: job.job_parameters,
|
||||
createTime: createTime,
|
||||
updateTime: updateTime,
|
||||
details: null
|
||||
});
|
||||
}
|
||||
return gcViewModels;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
export class GcJobData {
|
||||
id: number;
|
||||
job_name: string;
|
||||
job_kind: string;
|
||||
schedule: Schedule;
|
||||
job_status: string;
|
||||
job_parameters: string;
|
||||
job_uuid: string;
|
||||
creation_time: string;
|
||||
update_time: string;
|
||||
delete: boolean;
|
||||
}
|
||||
|
||||
export class Schedule {
|
||||
type: string;
|
||||
cron: string;
|
||||
}
|
||||
export class GcJobViewModel {
|
||||
id: number;
|
||||
type: string;
|
||||
status: string;
|
||||
parameters: string;
|
||||
createTime: Date;
|
||||
updateTime: Date;
|
||||
details: string;
|
||||
}
|
||||
|
||||
|
@ -242,3 +242,12 @@ export enum ResourceType {
|
||||
}
|
||||
|
||||
export const CARD_VIEW_LOCALSTORAGE_KEY = 'card-view';
|
||||
|
||||
export enum ScheduleType {
|
||||
NONE = "None",
|
||||
DAILY = "Daily",
|
||||
WEEKLY = "Weekly",
|
||||
HOURLY = "Hourly",
|
||||
CUSTOM = "Custom",
|
||||
MANUAL = 'Manual'
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user