mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 12:15:20 +01:00
Merge pull request #12675 from AllForNothing/p2p-3
Add name and endpoint check for p2p-preheat
This commit is contained in:
commit
f31b29a01c
@ -16,6 +16,7 @@ $refrsh-btn-color: #007CBB;
|
||||
}
|
||||
|
||||
.action-head-pos {
|
||||
padding-top: 12px;
|
||||
padding-right: 18px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
|
@ -33,24 +33,30 @@
|
||||
</clr-select-container>
|
||||
|
||||
<!-- 2. name -->
|
||||
<clr-input-container>
|
||||
<label class="required clr-control-label" for="name">{{
|
||||
'DISTRIBUTION.NAME' | translate
|
||||
}}</label>
|
||||
<input class="width-280"
|
||||
clrInput
|
||||
required
|
||||
type="text"
|
||||
id="name"
|
||||
autocomplete="off"
|
||||
[(ngModel)]="model.name"
|
||||
name="name"
|
||||
[disabled]="editingMode"
|
||||
/>
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control">
|
||||
<label class="required clr-control-label" for="name">{{'DISTRIBUTION.NAME' | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="((nameNg.dirty || nameNg.touched) && nameNg.invalid) || isNameExisting">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="width-280 clr-input"
|
||||
required
|
||||
type="text"
|
||||
id="name"
|
||||
autocomplete="off"
|
||||
[(ngModel)]="model.name"
|
||||
name="name"
|
||||
#nameNg="ngModel"
|
||||
[disabled]="editingMode"
|
||||
(input)="inputName()"
|
||||
/>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkNameOnGoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="((nameNg.dirty || nameNg.touched) && nameNg.invalid) || isNameExisting">
|
||||
<span *ngIf="!((nameNg.dirty || nameNg.touched) && nameNg.invalid) && isNameExisting">{{'SCANNER.NAME_EXISTS' | translate}}</span>
|
||||
<span *ngIf="(nameNg.dirty || nameNg.touched) && nameNg.invalid">{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. description -->
|
||||
<clr-textarea-container>
|
||||
@ -65,27 +71,32 @@
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
></textarea>
|
||||
</clr-textarea-container>
|
||||
|
||||
<!-- 4. endpoint -->
|
||||
<clr-input-container>
|
||||
<label class="required clr-control-label" for="endpoint">{{
|
||||
'DISTRIBUTION.ENDPOINT' | translate
|
||||
}}</label>
|
||||
<input class="width-280"
|
||||
clrInput
|
||||
required
|
||||
pattern="^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(.*?)*$"
|
||||
type="text"
|
||||
id="endpoint"
|
||||
placeholder="http(s)://192.168.1.1"
|
||||
[(ngModel)]="model.endpoint"
|
||||
name="endpoint"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<clr-control-error>{{
|
||||
'TOOLTIP.ENDPOINT_FORMAT' | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control">
|
||||
<label class="required clr-control-label" for="name">{{'DISTRIBUTION.ENDPOINT' | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="((endpointNg.dirty || endpointNg.touched) && endpointNg.invalid) || isEndpointExisting">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="width-280 clr-input"
|
||||
required
|
||||
pattern="^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(.*?)*$"
|
||||
type="text"
|
||||
id="endpoint"
|
||||
placeholder="http(s)://192.168.1.1"
|
||||
[(ngModel)]="model.endpoint"
|
||||
name="endpoint"
|
||||
#endpointNg="ngModel"
|
||||
autocomplete="off"
|
||||
(input)="inputEndpoint()"
|
||||
/>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkEndpointOngoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="((endpointNg.dirty || endpointNg.touched) && endpointNg.invalid) || isEndpointExisting">
|
||||
<span *ngIf="!((endpointNg.dirty || endpointNg.touched) && endpointNg.invalid) && isEndpointExisting">{{'SCANNER.ENDPOINT_EXISTS' | translate}}</span>
|
||||
<span *ngIf="(endpointNg.dirty || endpointNg.touched) && endpointNg.invalid">{{ 'TOOLTIP.ENDPOINT_FORMAT' | translate }}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<!-- auth mode -->
|
||||
<clr-radio-container clrInline>
|
||||
<label>{{ 'DISTRIBUTION.AUTH_MODE' | translate }}</label>
|
||||
@ -232,7 +243,7 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="button-test" type="button" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="!isValid || onTesting">{{'SCANNER.TEST_CONNECTION' | translate}}</button>
|
||||
<button id="button-test" type="button" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="!isValid || onTesting || checkNameOnGoing || checkEndpointOngoing || isEndpointExisting || isNameExisting">{{'SCANNER.TEST_CONNECTION' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
@ -241,7 +252,7 @@
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
(click)="submit()"
|
||||
[disabled]="!isValid || !hasChangesForEdit()"
|
||||
[disabled]="!isValid || !hasChangesForEdit() || checkNameOnGoing || checkEndpointOngoing"
|
||||
>
|
||||
{{ 'BUTTON.OK' | translate }}
|
||||
</button>
|
||||
|
@ -7,6 +7,8 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { DistributionSetupModalComponent } from './distribution-setup-modal.component';
|
||||
import { PreheatService } from "../../../../ng-swagger-gen/services/preheat.service";
|
||||
import { Instance } from '../../../../ng-swagger-gen/models/instance';
|
||||
import { of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
describe('DistributionSetupModalComponent', () => {
|
||||
let component: DistributionSetupModalComponent;
|
||||
@ -24,6 +26,11 @@ describe('DistributionSetupModalComponent', () => {
|
||||
vendor: 'kraken',
|
||||
status: 'Healthy'
|
||||
};
|
||||
const fakedPreheatService = {
|
||||
ListInstances() {
|
||||
return of([]).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -34,7 +41,7 @@ describe('DistributionSetupModalComponent', () => {
|
||||
HttpClientTestingModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [PreheatService],
|
||||
providers: [ { provide: PreheatService, useValue: fakedPreheatService }],
|
||||
declarations: [DistributionSetupModalComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
@ -1,18 +1,27 @@
|
||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { NgForm, Validators } from '@angular/forms';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { errorHandler } from '../../../lib/utils/shared/shared.utils';
|
||||
import { PreheatService } from '../../../../ng-swagger-gen/services/preheat.service';
|
||||
import { Instance } from '../../../../ng-swagger-gen/models/instance';
|
||||
import { AuthMode, FrontInstance, HEALTHY } from '../distribution-interface';
|
||||
import { AuthMode, FrontInstance } from '../distribution-interface';
|
||||
import { clone } from '../../../lib/utils/utils';
|
||||
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
import { Metadata } from '../../../../ng-swagger-gen/models/metadata';
|
||||
import { operateChanges, OperateInfo, OperationState } from '../../../lib/components/operation/operate';
|
||||
import { OperationService } from '../../../lib/components/operation/operation.service';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { debounceTime, distinctUntilChanged, filter, finalize, switchMap } from 'rxjs/operators';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
|
||||
const DEFAULT_PROVIDER: string = 'dragonfly';
|
||||
|
||||
@ -21,7 +30,7 @@ const DEFAULT_PROVIDER: string = 'dragonfly';
|
||||
templateUrl: './distribution-setup-modal.component.html',
|
||||
styleUrls: ['./distribution-setup-modal.component.scss']
|
||||
})
|
||||
export class DistributionSetupModalComponent implements OnInit {
|
||||
export class DistributionSetupModalComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
providers: Metadata[] = [];
|
||||
model: Instance;
|
||||
@ -37,6 +46,14 @@ export class DistributionSetupModalComponent implements OnInit {
|
||||
refresh: EventEmitter<any> = new EventEmitter<any>();
|
||||
checkBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
onTesting: boolean = false;
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _endpointSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
private _endpointSubscription: Subscription;
|
||||
isNameExisting: boolean = false;
|
||||
isEndpointExisting: boolean = false;
|
||||
checkNameOnGoing: boolean = false;
|
||||
checkEndpointOngoing: boolean = false;
|
||||
constructor(
|
||||
private distributionService: PreheatService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
@ -45,8 +62,72 @@ export class DistributionSetupModalComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscribeName();
|
||||
this.subscribeEndpoint();
|
||||
this.reset();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this._nameSubscription) {
|
||||
this._nameSubscription.unsubscribe();
|
||||
this._nameSubscription = null;
|
||||
}
|
||||
if (this._endpointSubscription) {
|
||||
this._endpointSubscription.unsubscribe();
|
||||
this._endpointSubscription = null;
|
||||
}
|
||||
}
|
||||
subscribeName() {
|
||||
if (!this._nameSubscription) {
|
||||
this._nameSubscription = this._nameSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
return name.length > 0;
|
||||
}),
|
||||
switchMap((name) => {
|
||||
this.isNameExisting = false;
|
||||
this.checkNameOnGoing = true;
|
||||
return this.distributionService.ListInstances({
|
||||
q: encodeURIComponent(`name=${name}`)
|
||||
}).pipe(finalize(() => this.checkNameOnGoing = false));
|
||||
}))
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isNameExisting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
subscribeEndpoint() {
|
||||
if (!this._endpointSubscription) {
|
||||
this._endpointSubscription = this._endpointSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(endpoint => {
|
||||
return this.instanceForm.control.get('endpoint').valid;
|
||||
}),
|
||||
switchMap((endpoint) => {
|
||||
this.isEndpointExisting = false;
|
||||
this.checkEndpointOngoing = true;
|
||||
return this.distributionService.ListInstances({
|
||||
q: encodeURIComponent(`endpoint=${endpoint}`)
|
||||
}).pipe(finalize(() => this.checkEndpointOngoing = false));
|
||||
}))
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isEndpointExisting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
inputName() {
|
||||
this._nameSubject.next(this.model.name);
|
||||
}
|
||||
inputEndpoint() {
|
||||
this._endpointSubject.next(this.model.endpoint);
|
||||
}
|
||||
public get isValid(): boolean {
|
||||
return this.instanceForm && this.instanceForm.valid;
|
||||
}
|
||||
@ -100,7 +181,10 @@ export class DistributionSetupModalComponent implements OnInit {
|
||||
auth_mode: AuthMode.NONE,
|
||||
auth_info: this.authData
|
||||
};
|
||||
this.instanceForm.reset();
|
||||
this.instanceForm.reset({
|
||||
enabled: true,
|
||||
insecure: true,
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
@ -190,6 +274,7 @@ export class DistributionSetupModalComponent implements OnInit {
|
||||
this.originModelForEdit = clone(data);
|
||||
this.authData = this.model.auth_info || {};
|
||||
} else {
|
||||
this.reset();
|
||||
if (this.providers && this.providers.length) {
|
||||
this.providers.forEach(item => {
|
||||
if (item.id === DEFAULT_PROVIDER) {
|
||||
|
@ -34,14 +34,16 @@
|
||||
<!-- name -->
|
||||
<div class="clr-form-control">
|
||||
<label for="name" class="clr-control-label required width-6rem">{{'P2P_PROVIDER.NAME' | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="name.errors && name.errors.required && (name.dirty || name.touched)">
|
||||
<div class="clr-control-container" [class.clr-error]="((name.dirty || name.touched) && name.invalid) || isNameExisting">
|
||||
<div class="clr-input-wrapper">
|
||||
<input pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" [disabled]="loading" autocomplete="off" class="clr-input width-380" type="text" id="name" [(ngModel)]="policy.name"
|
||||
size="30" name="name" #name="ngModel" required>
|
||||
size="30" name="name" #name="ngModel" required (input)="inputName()">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkNameOnGoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="name.errors && name.errors.required && (name.dirty || name.touched)" class="tooltip-content">
|
||||
{{'P2P_PROVIDER.NAME_REQUIRED' | translate}}
|
||||
<clr-control-error *ngIf="((name.dirty || name.touched) && name.invalid) || isNameExisting">
|
||||
<span *ngIf="!((name.dirty || name.touched) && name.invalid) && isNameExisting">{{'SCANNER.NAME_EXISTS' | translate}}</span>
|
||||
<span *ngIf="(name.dirty || name.touched) && name.invalid">{{ 'PROJECT.NAME_TOOLTIP' | translate }}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
@ -172,12 +174,12 @@
|
||||
</form>
|
||||
<div class="mt-1 bottom-btn" *ngIf="!isEdit">
|
||||
<button type="button" class="btn btn-outline" id="add-policy-cancel" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" id="new-policy" class="btn btn-primary" [clrLoading]="buttonStatus" [disabled]="loading || !valid()" (click)="addOrSave(true)">{{'BUTTON.ADD' | translate}}</button>
|
||||
<button type="button" id="new-policy" class="btn btn-primary" [clrLoading]="buttonStatus" [disabled]="isNameExisting ||checkNameOnGoing || loading || !valid()" (click)="addOrSave(true)">{{'BUTTON.ADD' | translate}}</button>
|
||||
</div>
|
||||
<div class="mt-1 bottom-btn" *ngIf="isEdit">
|
||||
<button type="button" class="btn btn-outline" id="edit-policy-cancel" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" id="edit-policy-save"
|
||||
[clrLoading]="buttonStatus" [disabled]="loading || !valid() || !hasChange()" (click)="addOrSave(false)">{{'BUTTON.SAVE' | translate}}</button>
|
||||
[clrLoading]="buttonStatus" [disabled]="isNameExisting || checkNameOnGoing || loading || !valid() || !hasChange()" (click)="addOrSave(false)">{{'BUTTON.SAVE' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,6 +33,9 @@ describe('AddP2pPolicyComponent', () => {
|
||||
},
|
||||
UpdatePolicy() {
|
||||
return of(true).pipe(delay(0));
|
||||
},
|
||||
ListPolicies() {
|
||||
return of([]).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
const mockActivatedRoute = {
|
||||
@ -135,7 +138,7 @@ describe('AddP2pPolicyComponent', () => {
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
const errorEle: HTMLElement = fixture.nativeElement.querySelector("clr-control-error");
|
||||
expect(errorEle.innerText).toEqual('P2P_PROVIDER.NAME_REQUIRED');
|
||||
expect(errorEle.innerText).toEqual('PROJECT.NAME_TOOLTIP');
|
||||
});
|
||||
it("save button should work", async () => {
|
||||
fixture.autoDetectChanges(true);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild, } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild, } from '@angular/core';
|
||||
import { PreheatPolicy } from '../../../../../ng-swagger-gen/models/preheat-policy';
|
||||
import { InlineAlertComponent } from '../../../shared/inline-alert/inline-alert.component';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { OriginCron, ProjectService } from '../../../../lib/services';
|
||||
import { CronScheduleComponent } from '../../../../lib/components/cron-schedule';
|
||||
import { PreheatService } from '../../../../../ng-swagger-gen/services/preheat.service';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { debounceTime, distinctUntilChanged, filter, finalize, switchMap } from 'rxjs/operators';
|
||||
import { deleteEmptyKey } from '../../../../lib/utils/utils';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
import { SessionService } from '../../../shared/session.service';
|
||||
@ -14,6 +14,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { FILTER_TYPE, PROJECT_SEVERITY_LEVEL_MAP, TRIGGER, TRIGGER_I18N_MAP } from '../p2p-provider.service';
|
||||
import { ProviderUnderProject } from '../../../../../ng-swagger-gen/models/provider-under-project';
|
||||
import { AppConfigService } from '../../../services/app-config.service';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
|
||||
const SCHEDULE_TYPE = {
|
||||
NONE: "None",
|
||||
@ -28,7 +29,7 @@ const TRUE: string = 'true';
|
||||
templateUrl: './add-p2p-policy.component.html',
|
||||
styleUrls: ['./add-p2p-policy.component.scss']
|
||||
})
|
||||
export class AddP2pPolicyComponent implements OnInit {
|
||||
export class AddP2pPolicyComponent implements OnInit, OnDestroy {
|
||||
severityOptions = [
|
||||
{severity: 5, severityLevel: 'VULNERABILITY.SEVERITY.CRITICAL'},
|
||||
{severity: 4, severityLevel: 'VULNERABILITY.SEVERITY.HIGH'},
|
||||
@ -73,7 +74,10 @@ export class AddP2pPolicyComponent implements OnInit {
|
||||
projectSeverity: string;
|
||||
triggers: string[] = [TRIGGER.MANUAL, TRIGGER.SCHEDULED, TRIGGER.EVENT_BASED];
|
||||
enableContentTrust: boolean = false;
|
||||
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
isNameExisting: boolean = false;
|
||||
checkNameOnGoing: boolean = false;
|
||||
constructor(private preheatService: PreheatService,
|
||||
private session: SessionService,
|
||||
private route: ActivatedRoute,
|
||||
@ -90,6 +94,43 @@ export class AddP2pPolicyComponent implements OnInit {
|
||||
// get latest project info
|
||||
this.getProject();
|
||||
}
|
||||
this.subscribeName();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this._nameSubscription) {
|
||||
this._nameSubscription.unsubscribe();
|
||||
this._nameSubscription = null;
|
||||
}
|
||||
}
|
||||
subscribeName() {
|
||||
if (!this._nameSubscription) {
|
||||
this._nameSubscription = this._nameSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
if (this.isEdit && this.originPolicyForEdit && this.originPolicyForEdit.name === name) {
|
||||
return false;
|
||||
}
|
||||
return name.length > 0;
|
||||
}),
|
||||
switchMap((name) => {
|
||||
this.isNameExisting = false;
|
||||
this.checkNameOnGoing = true;
|
||||
return this.preheatService.ListPolicies({
|
||||
projectName: this.projectName,
|
||||
q: encodeURIComponent(`name=${name}`)
|
||||
}).pipe(finalize(() => this.checkNameOnGoing = false));
|
||||
}))
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isNameExisting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
inputName() {
|
||||
this._nameSubject.next(this.policy.name);
|
||||
}
|
||||
getProject() {
|
||||
this.projectService.getProject(this.projectId)
|
||||
@ -115,6 +156,13 @@ export class AddP2pPolicyComponent implements OnInit {
|
||||
severity: PROJECT_SEVERITY_LEVEL_MAP[this.projectSeverity],
|
||||
onlySignedImages: this.enableContentTrust
|
||||
});
|
||||
if (this.providers && this.providers.length) {
|
||||
this.providers.forEach(item => {
|
||||
if (item.default) {
|
||||
this.policy.provider_id = item.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setCron(event: any) {
|
||||
|
@ -77,7 +77,7 @@
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'P2P_PROVIDER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.ENABLED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.PROVIDER' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'provider_name'">{{'P2P_PROVIDER.PROVIDER' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.FILTERS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.TRIGGER' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="creationTimeComparator">{{'P2P_PROVIDER.CREATED' | translate}}</clr-dg-column>
|
||||
@ -164,20 +164,19 @@
|
||||
<select id="selectKey" (change)="selectFilterKey($event)">
|
||||
<option value="id">{{"P2P_PROVIDER.ID" | translate | lowercase}}</option>
|
||||
<option value="status">{{"REPLICATION.STATUS" | translate | lowercase}}</option>
|
||||
<option value="vendor_type">{{"P2P_PROVIDER.PROVIDER_TYPE" | translate | lowercase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<hbr-filter (filterEvt)="doFilter($event)" [currentValue]="searchString" id="filter-executions" [withDivider]="true"
|
||||
(openFlag)="openFilter($event)" filterPlaceholder='{{"REPLICATION.FILTER_EXECUTIONS_PLACEHOLDER" | translate}}'></hbr-filter>
|
||||
<span class="refresh-btn">
|
||||
<clr-icon shape="refresh" (click)="refresh()" [hidden]="loading"></clr-icon>
|
||||
<clr-icon shape="refresh" (click)="refreshJobs()" [hidden]="loading"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column>{{'REPLICATION.ID' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.REPLICATION_TRIGGER' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.TRIGGER' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.DURATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.SUCCESS_RATE' | translate}}</clr-dg-column>
|
||||
@ -196,7 +195,7 @@
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{execution.trigger}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{getTriggerTypeI18nForExecution(execution.trigger) | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{execution.start_time | date: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{getDuration(execution)}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{getSuccessRate(execution.metrics)}}</clr-dg-cell>
|
||||
@ -206,7 +205,7 @@
|
||||
<span *ngIf="totalExecutionCount">{{pagination.firstItem + 1}}
|
||||
- {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}}</span>
|
||||
{{totalExecutionCount}} {{'REPLICATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [(clrDgPage)]="currentExecutionPage" [clrDgPageSize]="10"
|
||||
<clr-dg-pagination #pagination [(clrDgPage)]="currentExecutionPage" [clrDgPageSize]="pageSize"
|
||||
[clrDgTotalItems]="totalExecutionCount"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
@ -33,7 +33,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
.filter-tag {
|
||||
margin-top: 6px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
.filter-title {
|
||||
font-weight: 400;
|
||||
|
@ -11,7 +11,7 @@
|
||||
// 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 { distinctUntilChanged, finalize, switchMap } from 'rxjs/operators';
|
||||
import { debounceTime, distinctUntilChanged, finalize, switchMap } from 'rxjs/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
@ -106,10 +106,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
|
||||
this._searchSubscription.unsubscribe();
|
||||
this._searchSubscription = null;
|
||||
}
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
this.clearLoop();
|
||||
}
|
||||
getPermissions() {
|
||||
const permissionsList: Observable<boolean>[] = [];
|
||||
@ -134,7 +131,11 @@ export class PolicyComponent implements OnInit, OnDestroy {
|
||||
getProviders() {
|
||||
this.preheatService.ListProvidersUnderProject({projectName: this.projectName})
|
||||
.subscribe(res => {
|
||||
this.providers = res;
|
||||
if (res && res.length) {
|
||||
this.providers = res.filter(provider => {
|
||||
return provider.enabled;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
refresh() {
|
||||
@ -358,23 +359,13 @@ export class PolicyComponent implements OnInit, OnDestroy {
|
||||
}).pipe(finalize(() => this.jobsLoading = false))
|
||||
.subscribe(response => {
|
||||
if (response.headers) {
|
||||
let xHeader: string = response.headers.get('x-total-count');
|
||||
let xHeader: string = response.headers.get('X-Total-Count');
|
||||
if (xHeader) {
|
||||
this.totalExecutionCount = parseInt(xHeader, 0);
|
||||
}
|
||||
}
|
||||
this.executionList = response.body;
|
||||
if (this.executionList && this.executionList.length) {
|
||||
for (let i = 0; i < this.executionList.length; i++) {
|
||||
if (this.p2pProviderService.willChangStatus(this.executionList[i].status)) {
|
||||
if (!this.timeout) {
|
||||
this.timeout = setTimeout(() => {
|
||||
this.clrLoadJobs(null, false);
|
||||
}, TIME_OUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setLoop();
|
||||
}, error => {
|
||||
this.messageHandlerService.handleError(error);
|
||||
});
|
||||
@ -412,6 +403,12 @@ export class PolicyComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return TRIGGER_I18N_MAP[TRIGGER.MANUAL];
|
||||
}
|
||||
getTriggerTypeI18nForExecution(trigger: string) {
|
||||
if (trigger && TRIGGER_I18N_MAP[trigger]) {
|
||||
return TRIGGER_I18N_MAP[trigger];
|
||||
}
|
||||
return trigger;
|
||||
}
|
||||
isScheduled(trigger: string): boolean {
|
||||
return JSON.parse(trigger).type === TRIGGER.SCHEDULED;
|
||||
}
|
||||
@ -459,7 +456,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
|
||||
subscribeSearch() {
|
||||
if (!this._searchSubscription) {
|
||||
this._searchSubscription = this._searchSubject.pipe(
|
||||
distinctUntilChanged(),
|
||||
debounceTime(500),
|
||||
switchMap(searchString => {
|
||||
this.jobsLoading = true;
|
||||
let params: string;
|
||||
@ -481,10 +478,31 @@ export class PolicyComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
this.executionList = response.body;
|
||||
this.setLoop();
|
||||
});
|
||||
}
|
||||
}
|
||||
canStop(): boolean {
|
||||
return this.selectedExecutionRow && this.p2pProviderService.willChangStatus(this.selectedExecutionRow.status);
|
||||
}
|
||||
clearLoop() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
}
|
||||
setLoop() {
|
||||
this.clearLoop();
|
||||
if (this.executionList && this.executionList.length) {
|
||||
for (let i = 0; i < this.executionList.length; i++) {
|
||||
if (this.p2pProviderService.willChangStatus(this.executionList[i].status)) {
|
||||
if (!this.timeout) {
|
||||
this.timeout = setTimeout(() => {
|
||||
this.clrLoadJobs(null, false);
|
||||
}, TIME_OUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,25 +76,34 @@
|
||||
|
||||
<div class="tasks-detail">
|
||||
<h3 class="modal-title">{{'P2P_PROVIDER.TASKS' | translate}}</h3>
|
||||
<div class="row flex-items-xs-between flex-items-xs-bottom">
|
||||
<div class="action-select">
|
||||
<span class="refresh-btn">
|
||||
<clr-icon shape="refresh" (click)="refreshTasks()"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid (clrDgRefresh)="clrLoadTasks(true)" [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgField]="'id'">{{'REPLICATION.TASK_ID'| translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'execution_id'">{{'P2P_PROVIDER.ID' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-action-bar>
|
||||
<div class="row flex-end">
|
||||
<div class="select filter-tag clr-select-wrapper" [hidden]="!isOpenFilterTag">
|
||||
<select id="selectKey" (change)="selectFilterKey($event)">
|
||||
<option value="id">{{"REPLICATION.TASK_ID" | translate | lowercase}}</option>
|
||||
<option value="execution_id">{{"P2P_PROVIDER.ID" | translate | lowercase}}</option>
|
||||
<option value="status">{{"REPLICATION.STATUS" | translate | lowercase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<hbr-filter (filterEvt)="doFilter($event)" [currentValue]="searchString" id="filter-executions" [withDivider]="true"
|
||||
(openFlag)="openFilter($event)" filterPlaceholder='{{"REPLICATION.FILTER_PLACEHOLDER" | translate}}'></hbr-filter>
|
||||
<span class="refresh-btn">
|
||||
<clr-icon shape="refresh" (click)="refreshTasks()" [hidden]="loading"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column>{{'REPLICATION.TASK_ID'| translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.ID' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.ARTIFACT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.DIGEST' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'P2P_PROVIDER.TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.DURATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'P2P_PROVIDER.TASKS_PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let t of tasks">
|
||||
<clr-dg-row *ngFor="let t of tasks" [clrDgItem]="t">
|
||||
<clr-dg-cell>{{t.id}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.execution_id}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.status}}
|
||||
@ -124,8 +133,11 @@
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<span *ngIf="totalCount">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'REPLICATION.OF' | translate}} </span>{{totalCount }} {{'REPLICATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgTotalItems]="totalCount" [clrDgPageSize]="pageSize"></clr-dg-pagination>
|
||||
<span *ngIf="totalCount">{{pagination.firstItem + 1}}
|
||||
- {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}}</span>
|
||||
{{totalCount}} {{'REPLICATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize"
|
||||
[clrDgTotalItems]="totalCount"></clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
|
@ -103,7 +103,6 @@
|
||||
}
|
||||
}
|
||||
clr-datagrid {
|
||||
margin-top: 20px;
|
||||
.resource-width {
|
||||
width: 150px;
|
||||
}
|
||||
@ -113,3 +112,16 @@
|
||||
.margin-top-075 {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
.flex-end {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
.refresh-btn {
|
||||
margin-top: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.filter-tag {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import { TaskListComponent } from './task-list.component';
|
||||
import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service';
|
||||
import { UserPermissionService } from '../../../../lib/services';
|
||||
import { Task } from '../../../../../ng-swagger-gen/models/task';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
describe('TaskListComponent', () => {
|
||||
let component: TaskListComponent;
|
||||
let fixture: ComponentFixture<TaskListComponent>;
|
||||
@ -38,8 +39,13 @@ describe('TaskListComponent', () => {
|
||||
GetExecution() {
|
||||
return of(execution).pipe(delay(0));
|
||||
},
|
||||
ListTasks() {
|
||||
return of([task]).pipe(delay(0));
|
||||
ListTasksResponse() {
|
||||
return of(new HttpResponse({
|
||||
body: [task],
|
||||
headers: new HttpHeaders({
|
||||
"X-Total-Count": "1"
|
||||
})
|
||||
})).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
const mockActivatedRoute = {
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { clone, CustomComparator, DEFAULT_PAGE_SIZE, isEmptyObject } from '../../../../lib/utils/utils';
|
||||
import { debounceTime, finalize, switchMap } from 'rxjs/operators';
|
||||
import { clone, DEFAULT_PAGE_SIZE } from '../../../../lib/utils/utils';
|
||||
import { Task } from '../../../../../ng-swagger-gen/models/task';
|
||||
import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service';
|
||||
import { Project } from '../../project';
|
||||
import { ClrDatagridComparatorInterface, UserPermissionService, USERSTATICPERMISSION } from '../../../../lib/services';
|
||||
import { UserPermissionService, USERSTATICPERMISSION } from '../../../../lib/services';
|
||||
import { Execution } from '../../../../../ng-swagger-gen/models/execution';
|
||||
import { PreheatService } from '../../../../../ng-swagger-gen/services/preheat.service';
|
||||
import { EXECUTION_STATUS, P2pProviderService, TIME_OUT } from '../p2p-provider.service';
|
||||
import { forkJoin, Observable } from 'rxjs';
|
||||
import { forkJoin, Observable, Subject, Subscription } from 'rxjs';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
|
||||
@Component({
|
||||
@ -31,12 +31,15 @@ export class TaskListComponent implements OnInit, OnDestroy {
|
||||
stopOnGoing: boolean;
|
||||
executionId: string;
|
||||
preheatPolicyName: string;
|
||||
startTimeComparator: ClrDatagridComparatorInterface<Task> = new CustomComparator<Task>("start_time", "date");
|
||||
execution: Execution;
|
||||
hasUpdatePermission: boolean = false;
|
||||
btnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
timeout: any;
|
||||
timeoutForTaskList: any;
|
||||
searchString: string;
|
||||
private _searchSubject: Subject<string> = new Subject<string>();
|
||||
private _searchSubscription: Subscription;
|
||||
filterKey: string = 'id';
|
||||
constructor(
|
||||
private translate: TranslateService,
|
||||
private router: Router,
|
||||
@ -60,6 +63,37 @@ export class TaskListComponent implements OnInit, OnDestroy {
|
||||
this.getExecutionDetail(true);
|
||||
}
|
||||
this.getPermissions();
|
||||
this.subscribeSearch();
|
||||
}
|
||||
subscribeSearch() {
|
||||
if (!this._searchSubscription) {
|
||||
this._searchSubscription = this._searchSubject.pipe(
|
||||
debounceTime(500),
|
||||
switchMap(searchString => {
|
||||
this.loading = true;
|
||||
let params: string;
|
||||
if (this.searchString) {
|
||||
params = encodeURIComponent(`${this.filterKey}=~${searchString}`);
|
||||
}
|
||||
return this.preheatService.ListTasksResponse({
|
||||
projectName: this.projectName,
|
||||
preheatPolicyName: this.preheatPolicyName,
|
||||
executionId: +this.executionId,
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
q: params
|
||||
}).pipe(finalize(() => this.loading = false));
|
||||
})).subscribe(res => {
|
||||
if (res.headers) {
|
||||
let xHeader: string = res.headers.get('x-total-count');
|
||||
if (xHeader) {
|
||||
this.totalCount = parseInt(xHeader, 0);
|
||||
}
|
||||
}
|
||||
this.tasks = res.body;
|
||||
this.setLoop();
|
||||
});
|
||||
}
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
if (this.timeout) {
|
||||
@ -70,6 +104,10 @@ export class TaskListComponent implements OnInit, OnDestroy {
|
||||
clearTimeout(this.timeoutForTaskList);
|
||||
this.timeoutForTaskList = null;
|
||||
}
|
||||
if (this._searchSubscription) {
|
||||
this._searchSubscription.unsubscribe();
|
||||
this._searchSubscription = null;
|
||||
}
|
||||
}
|
||||
getPermissions() {
|
||||
const permissionsList: Observable<boolean>[] = [];
|
||||
@ -93,10 +131,14 @@ export class TaskListComponent implements OnInit, OnDestroy {
|
||||
projectName: this.projectName,
|
||||
preheatPolicyName: this.preheatPolicyName,
|
||||
executionId: +this.executionId
|
||||
}).pipe(finalize(() => (this.inProgress = false)))
|
||||
}).pipe(finalize(() => this.inProgress = false))
|
||||
.subscribe(res => {
|
||||
this.execution = res;
|
||||
if (!this.execution || this.p2pProviderService.willChangStatus(this.execution.status)) {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
if (!this.timeout) {
|
||||
this.timeout = setTimeout(() => {
|
||||
this.getExecutionDetail(false);
|
||||
@ -182,28 +224,30 @@ export class TaskListComponent implements OnInit, OnDestroy {
|
||||
if (withLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this.preheatService.ListTasks({
|
||||
let params: string;
|
||||
if (this.searchString) {
|
||||
params = encodeURIComponent(`${this.filterKey}=~${this.searchString}`);
|
||||
}
|
||||
this.preheatService.ListTasksResponse({
|
||||
projectName: this.projectName,
|
||||
preheatPolicyName: this.preheatPolicyName,
|
||||
executionId: +this.executionId
|
||||
executionId: +this.executionId,
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
q: params
|
||||
})
|
||||
.pipe(finalize(() => {
|
||||
this.loading = false;
|
||||
}))
|
||||
.subscribe(res => {
|
||||
this.tasks = res;
|
||||
if (this.tasks && this.tasks.length) {
|
||||
this.totalCount = this.tasks.length;
|
||||
for (let i = 0; i < this.tasks.length; i++) {
|
||||
if (this.p2pProviderService.willChangStatus(this.tasks[i].status)) {
|
||||
if (!this.timeoutForTaskList) {
|
||||
this.timeoutForTaskList = setTimeout(() => {
|
||||
this.clrLoadTasks(false);
|
||||
}, TIME_OUT);
|
||||
}
|
||||
}
|
||||
if (res.headers) {
|
||||
let xHeader: string = res.headers.get('x-total-count');
|
||||
if (xHeader) {
|
||||
this.totalCount = parseInt(xHeader, 0);
|
||||
}
|
||||
}
|
||||
this.tasks = res.body;
|
||||
this.setLoop();
|
||||
},
|
||||
error => {
|
||||
this.messageHandlerService.error(error);
|
||||
@ -233,4 +277,35 @@ export class TaskListComponent implements OnInit, OnDestroy {
|
||||
canStop(): boolean {
|
||||
return this.execution && this.p2pProviderService.willChangStatus(this.execution.status);
|
||||
}
|
||||
setLoop() {
|
||||
if (this.timeoutForTaskList) {
|
||||
clearTimeout(this.timeoutForTaskList);
|
||||
this.timeoutForTaskList = null;
|
||||
}
|
||||
if (this.tasks && this.tasks.length) {
|
||||
for (let i = 0; i < this.tasks.length; i++) {
|
||||
if (this.p2pProviderService.willChangStatus(this.tasks[i].status)) {
|
||||
if (!this.timeoutForTaskList) {
|
||||
this.timeoutForTaskList = setTimeout(() => {
|
||||
this.clrLoadTasks(false);
|
||||
}, TIME_OUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
selectFilterKey($event: any): void {
|
||||
this.filterKey = $event['target'].value;
|
||||
}
|
||||
doFilter(terms: string): void {
|
||||
this.searchString = terms;
|
||||
if (terms.trim()) {
|
||||
this._searchSubject.next(terms.trim());
|
||||
} else {
|
||||
this.clrLoadTasks(true);
|
||||
}
|
||||
}
|
||||
openFilter(isOpen: boolean): void {
|
||||
this.isOpenFilterTag = isOpen;
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +175,7 @@
|
||||
"LOGS": "日志",
|
||||
"TASKS": "任务",
|
||||
"API_EXPLORER": "API控制中心",
|
||||
"HARBOR_API_MANAGEMENT": "Harbor Api V2.0",
|
||||
"HARBOR_API_MANAGEMENT": "Harbor API V2.0",
|
||||
"HELM_API_MANAGEMENT": "Harbor Api",
|
||||
"DISTRIBUTIONS": {
|
||||
"NAME": "分布式分发",
|
||||
@ -289,7 +289,7 @@
|
||||
"SYS_ADMIN": "系统管理员",
|
||||
"PROJECT_ADMIN": "项目管理员",
|
||||
"PROJECT_MAINTAINER": "维护人员",
|
||||
"DEVELOPER": "开发人员",
|
||||
"DEVELOPER": "开发者",
|
||||
"GUEST": "访客",
|
||||
"LIMITED_GUEST": "受限访客",
|
||||
"DELETE": "删除",
|
||||
@ -503,7 +503,7 @@
|
||||
"SOURCE": "源",
|
||||
"DESTINATION": "目标",
|
||||
"POLICY": "政策",
|
||||
"DURATION": "到期时间",
|
||||
"DURATION": "持续时间",
|
||||
"SUCCESS_RATE": "成功百分比",
|
||||
"SUCCESS": "成功",
|
||||
"FAILURE": "失败",
|
||||
@ -1446,7 +1446,7 @@
|
||||
"ENDPOINT": "端点",
|
||||
"STATUS": "状态",
|
||||
"ENABLED": "启用",
|
||||
"SETUP_TIMESTAMP": "设置时间",
|
||||
"SETUP_TIMESTAMP": "创建时间",
|
||||
"PROVIDER": "供应商",
|
||||
"DELETION_TITLE": "删除实例",
|
||||
"DELETION_SUMMARY": "你确认删除实例 {{param}}?",
|
||||
|
Loading…
Reference in New Issue
Block a user