mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-07 08:27:43 +01:00
Merge pull request #12499 from AllForNothing/project-p2p
Add p2p-preheat policy UI
This commit is contained in:
commit
f73cd6fc0e
@ -18,7 +18,7 @@
|
|||||||
}}
|
}}
|
||||||
</button>
|
</button>
|
||||||
<button id="set-default"
|
<button id="set-default"
|
||||||
[disabled]="!(selectedRow && selectedRow.length === 1 && !selectedRow[0].default && !selectedRow[0].enabled)"
|
[disabled]="!(selectedRow && selectedRow.length === 1 && !selectedRow[0].default && selectedRow[0].enabled)"
|
||||||
class="btn btn-secondary"
|
class="btn btn-secondary"
|
||||||
(click)="setAsDefault()">{{'SCANNER.SET_AS_DEFAULT' | translate}}</button>
|
(click)="setAsDefault()">{{'SCANNER.SET_AS_DEFAULT' | translate}}</button>
|
||||||
<clr-dropdown
|
<clr-dropdown
|
||||||
@ -113,9 +113,14 @@
|
|||||||
</clr-signpost-content>
|
</clr-signpost-content>
|
||||||
</clr-signpost>
|
</clr-signpost>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell [ngSwitch]="instance.status === 'Healthy'">
|
<clr-dg-cell>
|
||||||
<span *ngSwitchCase="true" class="label label-success">{{ instance.status }}</span>
|
<span *ngIf="!instance.hasCheckHealth;else elseBlockLoading" class="spinner spinner-inline ml-2"></span>
|
||||||
<span *ngSwitchDefault class="label label-danger">{{ instance.status }}</span>
|
<ng-template #elseBlockLoading>
|
||||||
|
<span *ngIf="instance.pingStatus === 'Healthy';else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||||
|
<ng-template #elseBlock>
|
||||||
|
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||||
|
</ng-template>
|
||||||
|
</ng-template>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell>{{ instance.enabled || false }}</clr-dg-cell>
|
<clr-dg-cell>{{ instance.enabled || false }}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{ instance.auth_mode }}</clr-dg-cell>
|
<clr-dg-cell>{{ instance.auth_mode }}</clr-dg-cell>
|
||||||
@ -124,7 +129,7 @@
|
|||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount" [(clrDgPage)]="currentPage">
|
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount" [(clrDgPage)]="currentPage">
|
||||||
<span *ngIf="pagination.totalItems">{{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }}{{ 'HELM_CHART.OF' | translate }}</span>
|
<span *ngIf="pagination.totalItems">{{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }} {{ 'HELM_CHART.OF' | translate }} </span>
|
||||||
<span>{{ pagination.totalItems }} {{ 'HELM_CHART.ITEMS' | translate }}</span>
|
<span>{{ pagination.totalItems }} {{ 'HELM_CHART.ITEMS' | translate }}</span>
|
||||||
</clr-dg-pagination>
|
</clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
|
@ -90,6 +90,9 @@ describe('DistributionInstanceComponent', () => {
|
|||||||
},
|
},
|
||||||
ListProviders() {
|
ListProviders() {
|
||||||
return of(mockedProviders).pipe(delay(10));
|
return of(mockedProviders).pipe(delay(10));
|
||||||
|
},
|
||||||
|
PingInstances() {
|
||||||
|
return of(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import { clone, DEFAULT_PAGE_SIZE } from '../../../lib/utils/utils';
|
|||||||
import { Instance } from "../../../../ng-swagger-gen/models/instance";
|
import { Instance } from "../../../../ng-swagger-gen/models/instance";
|
||||||
import { PreheatService } from "../../../../ng-swagger-gen/services/preheat.service";
|
import { PreheatService } from "../../../../ng-swagger-gen/services/preheat.service";
|
||||||
import { Metadata } from '../../../../ng-swagger-gen/models/metadata';
|
import { Metadata } from '../../../../ng-swagger-gen/models/metadata';
|
||||||
|
import { FrontInstance, HEALTHY, UNHEALTHY } from '../distribution-interface';
|
||||||
|
|
||||||
interface MultiOperateData {
|
interface MultiOperateData {
|
||||||
operation: string;
|
operation: string;
|
||||||
@ -38,14 +39,15 @@ const KRAKEN_ICON: string = 'images/kraken-logo-color.svg';
|
|||||||
const ONE_THOUSAND: number = 1000;
|
const ONE_THOUSAND: number = 1000;
|
||||||
const KRAKEN: string = 'kraken';
|
const KRAKEN: string = 'kraken';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dist-instances',
|
selector: 'dist-instances',
|
||||||
templateUrl: './distribution-instances.component.html',
|
templateUrl: './distribution-instances.component.html',
|
||||||
styleUrls: ['./distribution-instances.component.scss']
|
styleUrls: ['./distribution-instances.component.scss']
|
||||||
})
|
})
|
||||||
export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
||||||
instances: Instance[] = [];
|
instances: FrontInstance[] = [];
|
||||||
selectedRow: Instance[] = [];
|
selectedRow: FrontInstance[] = [];
|
||||||
|
|
||||||
pageSize: number = DEFAULT_PAGE_SIZE;
|
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||||
currentPage: number = 1;
|
currentPage: number = 1;
|
||||||
@ -87,7 +89,6 @@ export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.loadData();
|
|
||||||
this.getProviders();
|
this.getProviders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,10 +133,24 @@ export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
|||||||
response.headers.get('x-total-count')
|
response.headers.get('x-total-count')
|
||||||
);
|
);
|
||||||
this.instances = response.body as Instance[];
|
this.instances = response.body as Instance[];
|
||||||
|
this.pingInstances();
|
||||||
},
|
},
|
||||||
err => this.msgHandler.error(err)
|
err => this.msgHandler.error(err)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
pingInstances() {
|
||||||
|
if (this.instances && this.instances.length) {
|
||||||
|
this.instances.forEach((item, index) => {
|
||||||
|
this.disService.PingInstances({instance: this.handleInstance(item)})
|
||||||
|
.pipe(finalize(() => this.instances[index].hasCheckHealth = true))
|
||||||
|
.subscribe(res => {
|
||||||
|
this.instances[index].pingStatus = HEALTHY;
|
||||||
|
}, error => {
|
||||||
|
this.instances[index].pingStatus = UNHEALTHY;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.queryString = null;
|
this.queryString = null;
|
||||||
@ -170,7 +185,7 @@ export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
|||||||
const instance: Instance = clone(this.selectedRow[0]);
|
const instance: Instance = clone(this.selectedRow[0]);
|
||||||
instance.default = true;
|
instance.default = true;
|
||||||
this.disService.UpdateInstance({
|
this.disService.UpdateInstance({
|
||||||
instance: instance,
|
instance: this.handleInstance(instance),
|
||||||
preheatInstanceName: this.selectedRow[0].name
|
preheatInstanceName: this.selectedRow[0].name
|
||||||
})
|
})
|
||||||
.subscribe(
|
.subscribe(
|
||||||
@ -309,7 +324,7 @@ export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
|||||||
copiedInstance.enabled = true;
|
copiedInstance.enabled = true;
|
||||||
return this.disService
|
return this.disService
|
||||||
.UpdateInstance({
|
.UpdateInstance({
|
||||||
instance: copiedInstance,
|
instance: this.handleInstance(copiedInstance),
|
||||||
preheatInstanceName: instance.name
|
preheatInstanceName: instance.name
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -327,7 +342,7 @@ export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
|||||||
this.msgHandler.error(msg + ': ' + errMsg);
|
this.msgHandler.error(msg + ': ' + errMsg);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return observableThrowError(message);
|
return observableThrowError(error);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -343,7 +358,7 @@ export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
|||||||
copiedInstance.enabled = false;
|
copiedInstance.enabled = false;
|
||||||
return this.disService
|
return this.disService
|
||||||
.UpdateInstance({
|
.UpdateInstance({
|
||||||
instance: copiedInstance,
|
instance: this.handleInstance(copiedInstance),
|
||||||
preheatInstanceName: instance.name
|
preheatInstanceName: instance.name
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -361,7 +376,7 @@ export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
|||||||
this.msgHandler.error(msg + ': ' + errMsg);
|
this.msgHandler.error(msg + ': ' + errMsg);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return observableThrowError(message);
|
return observableThrowError(error);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -379,4 +394,13 @@ export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
handleInstance(instance: FrontInstance): FrontInstance {
|
||||||
|
if (instance) {
|
||||||
|
const copyOne: FrontInstance = clone(instance);
|
||||||
|
delete copyOne.hasCheckHealth;
|
||||||
|
delete copyOne.pingStatus;
|
||||||
|
return copyOne;
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Instance } from '../../../ng-swagger-gen/models/instance';
|
||||||
|
|
||||||
export class AuthMode {
|
export class AuthMode {
|
||||||
static NONE = 'NONE';
|
static NONE = 'NONE';
|
||||||
static BASIC = 'BASIC';
|
static BASIC = 'BASIC';
|
||||||
@ -14,3 +16,11 @@ export enum PreheatingStatusEnum {
|
|||||||
SUCCESS = 'SUCCESS',
|
SUCCESS = 'SUCCESS',
|
||||||
FAIL = 'FAIL',
|
FAIL = 'FAIL',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FrontInstance extends Instance {
|
||||||
|
hasCheckHealth?: boolean;
|
||||||
|
pingStatus?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HEALTHY: string = 'Healthy';
|
||||||
|
export const UNHEALTHY: string = 'Unhealthy';
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<label class="required">{{
|
<label class="required">{{
|
||||||
'DISTRIBUTION.PROVIDER' | translate
|
'DISTRIBUTION.PROVIDER' | translate
|
||||||
}}</label>
|
}}</label>
|
||||||
<select
|
<select class="width-280"
|
||||||
clrSelect
|
clrSelect
|
||||||
name="provider"
|
name="provider"
|
||||||
id="provider"
|
id="provider"
|
||||||
@ -37,13 +37,12 @@
|
|||||||
<label class="required clr-control-label" for="name">{{
|
<label class="required clr-control-label" for="name">{{
|
||||||
'DISTRIBUTION.NAME' | translate
|
'DISTRIBUTION.NAME' | translate
|
||||||
}}</label>
|
}}</label>
|
||||||
<input
|
<input class="width-280"
|
||||||
clrInput
|
clrInput
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
placeholder="{{ 'DISTRIBUTION.SETUP.NAME_PLACEHOLDER' | translate }}"
|
|
||||||
[(ngModel)]="model.name"
|
[(ngModel)]="model.name"
|
||||||
name="name"
|
name="name"
|
||||||
[disabled]="editingMode"
|
[disabled]="editingMode"
|
||||||
@ -60,11 +59,8 @@
|
|||||||
clrTextarea
|
clrTextarea
|
||||||
type="text"
|
type="text"
|
||||||
id="description"
|
id="description"
|
||||||
class="inputWidth"
|
class="width-280"
|
||||||
row="3"
|
row="3"
|
||||||
placeholder="{{
|
|
||||||
'DISTRIBUTION.SETUP.DESCRIPTION_PLACEHOLDER' | translate
|
|
||||||
}}"
|
|
||||||
[(ngModel)]="model.description"
|
[(ngModel)]="model.description"
|
||||||
[ngModelOptions]="{ standalone: true }"
|
[ngModelOptions]="{ standalone: true }"
|
||||||
></textarea>
|
></textarea>
|
||||||
@ -75,15 +71,13 @@
|
|||||||
<label class="required clr-control-label" for="endpoint">{{
|
<label class="required clr-control-label" for="endpoint">{{
|
||||||
'DISTRIBUTION.ENDPOINT' | translate
|
'DISTRIBUTION.ENDPOINT' | translate
|
||||||
}}</label>
|
}}</label>
|
||||||
<input
|
<input class="width-280"
|
||||||
clrInput
|
clrInput
|
||||||
required
|
required
|
||||||
pattern="^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(.*?)*$"
|
pattern="^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(.*?)*$"
|
||||||
type="text"
|
type="text"
|
||||||
id="endpoint"
|
id="endpoint"
|
||||||
placeholder="{{
|
placeholder="http(s)://192.168.1.1"
|
||||||
'DISTRIBUTION.SETUP.ENDPOINT_PLACEHOLDER' | translate
|
|
||||||
}}"
|
|
||||||
[(ngModel)]="model.endpoint"
|
[(ngModel)]="model.endpoint"
|
||||||
name="endpoint"
|
name="endpoint"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@ -92,23 +86,6 @@
|
|||||||
'TOOLTIP.ENDPOINT_FORMAT' | translate
|
'TOOLTIP.ENDPOINT_FORMAT' | translate
|
||||||
}}</clr-control-error>
|
}}</clr-control-error>
|
||||||
</clr-input-container>
|
</clr-input-container>
|
||||||
|
|
||||||
<!-- 5. enabled -->
|
|
||||||
<clr-checkbox-container *ngIf="!editingMode">
|
|
||||||
<label for="enabled">
|
|
||||||
<span>{{ 'DISTRIBUTION.ENABLED' | translate }}</span>
|
|
||||||
</label>
|
|
||||||
<clr-checkbox-wrapper>
|
|
||||||
<input
|
|
||||||
clrCheckbox
|
|
||||||
id="enabled"
|
|
||||||
name="enabled"
|
|
||||||
type="checkbox"
|
|
||||||
[(ngModel)]="model.enabled"
|
|
||||||
/>
|
|
||||||
</clr-checkbox-wrapper>
|
|
||||||
</clr-checkbox-container>
|
|
||||||
|
|
||||||
<!-- auth mode -->
|
<!-- auth mode -->
|
||||||
<clr-radio-container clrInline>
|
<clr-radio-container clrInline>
|
||||||
<label>{{ 'DISTRIBUTION.AUTH_MODE' | translate }}</label>
|
<label>{{ 'DISTRIBUTION.AUTH_MODE' | translate }}</label>
|
||||||
@ -152,7 +129,6 @@
|
|||||||
<label for="token_mode">OAuth</label>
|
<label for="token_mode">OAuth</label>
|
||||||
</clr-radio-wrapper>
|
</clr-radio-wrapper>
|
||||||
</clr-radio-container>
|
</clr-radio-container>
|
||||||
|
|
||||||
<!-- auth data -->
|
<!-- auth data -->
|
||||||
<span *ngIf="model.auth_mode == 'BASIC'">
|
<span *ngIf="model.auth_mode == 'BASIC'">
|
||||||
<clr-input-container>
|
<clr-input-container>
|
||||||
@ -160,6 +136,7 @@
|
|||||||
'DISTRIBUTION.USERNAME' | translate
|
'DISTRIBUTION.USERNAME' | translate
|
||||||
}}</label>
|
}}</label>
|
||||||
<input
|
<input
|
||||||
|
class="width-280"
|
||||||
clrInput
|
clrInput
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
@ -180,6 +157,7 @@
|
|||||||
'DISTRIBUTION.PASSWORD' | translate
|
'DISTRIBUTION.PASSWORD' | translate
|
||||||
}}</label>
|
}}</label>
|
||||||
<input
|
<input
|
||||||
|
class="width-280"
|
||||||
clrInput
|
clrInput
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
@ -202,6 +180,7 @@
|
|||||||
'DISTRIBUTION.TOKEN' | translate
|
'DISTRIBUTION.TOKEN' | translate
|
||||||
}}</label>
|
}}</label>
|
||||||
<input
|
<input
|
||||||
|
class="width-280"
|
||||||
clrInput
|
clrInput
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
@ -219,9 +198,41 @@
|
|||||||
</clr-input-container>
|
</clr-input-container>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="model.auth_mode == 'NONE'"></span>
|
<span *ngIf="model.auth_mode == 'NONE'"></span>
|
||||||
|
|
||||||
|
<!-- 5. enabled -->
|
||||||
|
<div class="clr-form-control">
|
||||||
|
<label class="clr-control-label">{{"SCANNER.OPTIONS" | translate}}</label>
|
||||||
|
<div class="clr-control-container padding-top-3">
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input
|
||||||
|
clrCheckbox
|
||||||
|
id="enabled"
|
||||||
|
name="enabled"
|
||||||
|
type="checkbox"
|
||||||
|
[(ngModel)]="model.enabled"
|
||||||
|
/>
|
||||||
|
<label for="enabled"><span>{{ 'DISTRIBUTION.ENABLED' | translate }}</span></label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input name="insecure" clrCheckbox
|
||||||
|
type="checkbox" id="insecure"
|
||||||
|
[(ngModel)]="model.insecure"
|
||||||
|
>
|
||||||
|
<label for="insecure">{{"SCANNER.SKIP" | translate}}
|
||||||
|
<clr-tooltip>
|
||||||
|
<clr-icon class="color-57" clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||||
|
<clr-tooltip-content clrPosition="top-left" clrSize="md" *clrIfOpen>
|
||||||
|
{{'SCANNER.SKIP_CERT_VERIFY' | translate}}
|
||||||
|
</clr-tooltip-content>
|
||||||
|
</clr-tooltip>
|
||||||
|
</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<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 type="button" class="btn btn-outline" (click)="cancel()">
|
<button type="button" class="btn btn-outline" (click)="cancel()">
|
||||||
{{ 'BUTTON.CANCEL' | translate }}
|
{{ 'BUTTON.CANCEL' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
.display-none {
|
.display-none {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.width-280 {
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
import { MessageHandlerService } from '../../shared/message-handler/message-handler.service';
|
||||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm, Validators } from '@angular/forms';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { errorHandler } from '../../../lib/utils/shared/shared.utils';
|
import { errorHandler } from '../../../lib/utils/shared/shared.utils';
|
||||||
import { PreheatService } from "../../../../ng-swagger-gen/services/preheat.service";
|
import { PreheatService } from '../../../../ng-swagger-gen/services/preheat.service';
|
||||||
import { Instance } from "../../../../ng-swagger-gen/models/instance";
|
import { Instance } from '../../../../ng-swagger-gen/models/instance';
|
||||||
import { AuthMode } from "../distribution-interface";
|
import { AuthMode, FrontInstance, HEALTHY } from '../distribution-interface';
|
||||||
import { clone } from '../../../lib/utils/utils';
|
import { clone } from '../../../lib/utils/utils';
|
||||||
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
|
import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.component';
|
||||||
import { ClrLoadingState } from "@clr/angular";
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
import { Metadata } from '../../../../ng-swagger-gen/models/metadata';
|
import { Metadata } from '../../../../ng-swagger-gen/models/metadata';
|
||||||
import { operateChanges, OperateInfo, OperationState } from '../../../lib/components/operation/operate';
|
import { operateChanges, OperateInfo, OperationState } from '../../../lib/components/operation/operate';
|
||||||
import { OperationService } from '../../../lib/components/operation/operation.service';
|
import { OperationService } from '../../../lib/components/operation/operation.service';
|
||||||
|
import { finalize } from 'rxjs/operators';
|
||||||
|
|
||||||
|
const DEFAULT_PROVIDER: string = 'dragonfly';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dist-setup-modal',
|
selector: 'dist-setup-modal',
|
||||||
@ -32,7 +35,8 @@ export class DistributionSetupModalComponent implements OnInit {
|
|||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
refresh: EventEmitter<any> = new EventEmitter<any>();
|
refresh: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
checkBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
onTesting: boolean = false;
|
||||||
constructor(
|
constructor(
|
||||||
private distributionService: PreheatService,
|
private distributionService: PreheatService,
|
||||||
private msgHandler: MessageHandlerService,
|
private msgHandler: MessageHandlerService,
|
||||||
@ -43,7 +47,6 @@ export class DistributionSetupModalComponent implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isValid(): boolean {
|
public get isValid(): boolean {
|
||||||
return this.instanceForm && this.instanceForm.valid;
|
return this.instanceForm && this.instanceForm.valid;
|
||||||
}
|
}
|
||||||
@ -92,6 +95,7 @@ export class DistributionSetupModalComponent implements OnInit {
|
|||||||
name: '',
|
name: '',
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
insecure: true,
|
||||||
vendor: '',
|
vendor: '',
|
||||||
auth_mode: AuthMode.NONE,
|
auth_mode: AuthMode.NONE,
|
||||||
auth_info: this.authData
|
auth_info: this.authData
|
||||||
@ -118,7 +122,7 @@ export class DistributionSetupModalComponent implements OnInit {
|
|||||||
instance.description = this.model.description;
|
instance.description = this.model.description;
|
||||||
instance.auth_mode = this.model.auth_mode;
|
instance.auth_mode = this.model.auth_mode;
|
||||||
instance.auth_info = this.model.auth_info;
|
instance.auth_info = this.model.auth_info;
|
||||||
this.distributionService.UpdateInstance({preheatInstanceName: this.model.name, instance: instance
|
this.distributionService.UpdateInstance({preheatInstanceName: this.model.name, instance: this.handleInstance(instance)
|
||||||
}).subscribe(
|
}).subscribe(
|
||||||
response => {
|
response => {
|
||||||
this.translate.get('DISTRIBUTION.UPDATE_SUCCESS').subscribe(msg => {
|
this.translate.get('DISTRIBUTION.UPDATE_SUCCESS').subscribe(msg => {
|
||||||
@ -185,6 +189,14 @@ export class DistributionSetupModalComponent implements OnInit {
|
|||||||
this.model = clone(data);
|
this.model = clone(data);
|
||||||
this.originModelForEdit = clone(data);
|
this.originModelForEdit = clone(data);
|
||||||
this.authData = this.model.auth_info || {};
|
this.authData = this.model.auth_info || {};
|
||||||
|
} else {
|
||||||
|
if (this.providers && this.providers.length) {
|
||||||
|
this.providers.forEach(item => {
|
||||||
|
if (item.id === DEFAULT_PROVIDER) {
|
||||||
|
this.model.vendor = item.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +208,14 @@ export class DistributionSetupModalComponent implements OnInit {
|
|||||||
if ( this.model.endpoint !== this.originModelForEdit.endpoint) {
|
if ( this.model.endpoint !== this.originModelForEdit.endpoint) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if ( this.model.enabled != this.originModelForEdit.enabled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if ( this.model.insecure != this.originModelForEdit.insecure) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (this.model.auth_mode !== this.originModelForEdit.auth_mode) {
|
if (this.model.auth_mode !== this.originModelForEdit.auth_mode) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -217,4 +237,32 @@ export class DistributionSetupModalComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTestEndpoint() {
|
||||||
|
this.onTesting = true;
|
||||||
|
this.checkBtnState = ClrLoadingState.LOADING;
|
||||||
|
const instance: Instance = clone(this.model);
|
||||||
|
instance.id = 0;
|
||||||
|
this.distributionService.PingInstances({
|
||||||
|
instance: this.handleInstance(instance)
|
||||||
|
}).pipe(finalize(() => this.onTesting = false))
|
||||||
|
.subscribe(res => {
|
||||||
|
this.checkBtnState = ClrLoadingState.SUCCESS;
|
||||||
|
this.inlineAlert.showInlineSuccess({
|
||||||
|
message: "SCANNER.TEST_PASS"
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
this.inlineAlert.showInlineError('P2P_PROVIDER.TEST_FAILED');
|
||||||
|
this.checkBtnState = ClrLoadingState.ERROR;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleInstance(instance: FrontInstance): Instance {
|
||||||
|
if (instance) {
|
||||||
|
const copyOne: FrontInstance = clone(instance);
|
||||||
|
delete copyOne.hasCheckHealth;
|
||||||
|
delete copyOne.pingStatus;
|
||||||
|
return copyOne;
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,9 @@ import { ReplicationTasksComponent } from "../lib/components/replication/replica
|
|||||||
import { ReplicationTasksRoutingResolverService } from "./services/routing-resolvers/replication-tasks-routing-resolver.service";
|
import { ReplicationTasksRoutingResolverService } from "./services/routing-resolvers/replication-tasks-routing-resolver.service";
|
||||||
import { ArtifactDetailRoutingResolverService } from "./services/routing-resolvers/artifact-detail-routing-resolver.service";
|
import { ArtifactDetailRoutingResolverService } from "./services/routing-resolvers/artifact-detail-routing-resolver.service";
|
||||||
import { DistributionInstancesComponent } from './distribution/distribution-instances/distribution-instances.component';
|
import { DistributionInstancesComponent } from './distribution/distribution-instances/distribution-instances.component';
|
||||||
|
import { PolicyComponent } from './project/p2p-provider/policy/policy.component';
|
||||||
|
import { TaskListComponent } from './project/p2p-provider/task-list/task-list.component';
|
||||||
|
import { P2pProviderComponent } from './project/p2p-provider/p2p-provider.component';
|
||||||
|
|
||||||
const harborRoutes: Routes = [
|
const harborRoutes: Routes = [
|
||||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||||
@ -327,6 +330,28 @@ const harborRoutes: Routes = [
|
|||||||
},
|
},
|
||||||
component: ScannerComponent
|
component: ScannerComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'p2p-provider',
|
||||||
|
canActivate: [MemberPermissionGuard],
|
||||||
|
data: {
|
||||||
|
permissionParam: {
|
||||||
|
resource: USERSTATICPERMISSION.P2P_PROVIDER.KEY,
|
||||||
|
action: USERSTATICPERMISSION.P2P_PROVIDER.VALUE.READ
|
||||||
|
}
|
||||||
|
},
|
||||||
|
component: P2pProviderComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'policies',
|
||||||
|
component: PolicyComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':preheatPolicyName/executions/:executionId/tasks',
|
||||||
|
component: TaskListComponent
|
||||||
|
},
|
||||||
|
{ path: '', redirectTo: 'policies', pathMatch: 'full' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'repositories',
|
redirectTo: 'repositories',
|
||||||
|
@ -0,0 +1,172 @@
|
|||||||
|
<clr-modal [(clrModalOpen)]="isOpen" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||||
|
<h3 *ngIf="isEdit" class="modal-title">{{'P2P_PROVIDER.EDIT_POLICY' | translate}}</h3>
|
||||||
|
<h3 *ngIf="!isEdit" class="modal-title">{{'P2P_PROVIDER.ADD_POLICY' | translate}}</h3>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="align-center">
|
||||||
|
<inline-alert class="modal-title"></inline-alert>
|
||||||
|
<form #policyForm="ngForm" class="clr-form clr-form-horizontal">
|
||||||
|
<section class="form-block">
|
||||||
|
<!-- provider -->
|
||||||
|
<clr-select-container>
|
||||||
|
<label class="clr-control-label required width-6rem">{{'P2P_PROVIDER.PROVIDER' | translate}}</label>
|
||||||
|
<select class="width-380" [disabled]="loading" [class.clr-error]="provider.errors && provider.errors.required && (provider.dirty || provider.touched)"
|
||||||
|
#provider="ngModel" clrSelect
|
||||||
|
name="provider" id="provider"
|
||||||
|
[(ngModel)]="policy.provider_id"
|
||||||
|
required>
|
||||||
|
<option class="display-none" value=""></option>
|
||||||
|
<option [selected]="policy.provider_id == provider.id" *ngFor="let provider of providers" value="{{provider.id}}">{{provider.provider}}</option>
|
||||||
|
</select>
|
||||||
|
<clr-control-error *ngIf="provider.errors && provider.errors.required && (provider.dirty || provider.touched)">
|
||||||
|
{{'P2P_PROVIDER.PROVIDER_REQUIRED' | translate}}
|
||||||
|
</clr-control-error>
|
||||||
|
</clr-select-container>
|
||||||
|
<div class="clr-form-control mt-0" *ngIf="isSystemAdmin() &&!(providers && providers.length)">
|
||||||
|
<label class="clr-control-label width-6rem"></label>
|
||||||
|
<div class="clr-control-container width-380">
|
||||||
|
<div class="space-between">
|
||||||
|
<span class="alert-label">{{"P2P_PROVIDER.NO_PROVIDER" | translate}}</span>
|
||||||
|
<a class="go-link" routerLink="/harbor/distribution/instances">{{'P2P_PROVIDER.PROVIDER' | translate}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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-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>
|
||||||
|
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- description -->
|
||||||
|
<div class="clr-form-control">
|
||||||
|
<label for="description" class="clr-control-label width-6rem">{{'P2P_PROVIDER.DESCRIPTION' | translate}}</label>
|
||||||
|
<div class="clr-control-container">
|
||||||
|
<textarea autocomplete="off" class="clr-textarea width-380" type="text" id="description" #description="ngModel" [disabled]="loading" [(ngModel)]="policy.description"
|
||||||
|
name="description"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- filters-repo -->
|
||||||
|
<div class="clr-form-control">
|
||||||
|
<label for="repo" class="clr-control-label required width-6rem">{{'P2P_PROVIDER.FILTERS' | translate}}</label>
|
||||||
|
<div class="clr-control-container" [class.clr-error]="repo.errors && repo.errors.required && (repo.dirty || repo.touched)">
|
||||||
|
<div class="clr-input-wrapper">
|
||||||
|
<label class="sub-label">{{'P2P_PROVIDER.REPOS' | translate}}</label>
|
||||||
|
<input placeholder="**" [disabled]="loading" autocomplete="off" class="clr-input width-290" type="text" id="repo" [(ngModel)]="repos"
|
||||||
|
size="30" name="repo" #repo="ngModel" required>
|
||||||
|
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||||
|
</div>
|
||||||
|
<clr-control-helper class="margin-left-90px opacity-08">{{'TAG_RETENTION.REP_SEPARATOR' | translate}}</clr-control-helper>
|
||||||
|
<clr-control-error class="margin-left-90px" *ngIf="repo.errors && repo.errors.required && (repo.dirty || repo.touched)">
|
||||||
|
{{'P2P_PROVIDER.REPO_REQUIRED' | translate}}
|
||||||
|
</clr-control-error>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- filters-tag -->
|
||||||
|
<div class="clr-form-control margin-top-06">
|
||||||
|
<label for="repo" class="width-6rem"></label>
|
||||||
|
<div class="clr-control-container" [class.clr-error]="tag.errors && tag.errors.required && (tag.dirty || tag.touched)">
|
||||||
|
<div class="clr-input-wrapper">
|
||||||
|
<label class="sub-label">{{'P2P_PROVIDER.TAGS' | translate}}</label>
|
||||||
|
<input placeholder="**" [disabled]="loading" autocomplete="off" class="clr-input width-290" type="text" id="tag" [(ngModel)]="tags"
|
||||||
|
size="30" name="tag" #tag="ngModel" required>
|
||||||
|
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||||
|
</div>
|
||||||
|
<clr-control-helper class="margin-left-90px opacity-08">{{'P2P_PROVIDER.TAG_SEPARATOR' | translate}}</clr-control-helper>
|
||||||
|
<clr-control-error class="margin-left-90px" *ngIf="tag.errors && tag.errors.required && (tag.dirty || tag.touched)">
|
||||||
|
{{'P2P_PROVIDER.TAG_REQUIRED' | translate}}
|
||||||
|
</clr-control-error>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<clr-checkbox-container class="margin-top-06" *ngIf="withNotary()">
|
||||||
|
<label class="width-6rem">{{'P2P_PROVIDER.CRITERIA' | translate}}</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input clrCheckbox type="checkbox" id="onlySignedImages" [(ngModel)]="onlySignedImages" name="onlySignedImages">
|
||||||
|
<label for="onlySignedImages">{{'P2P_PROVIDER.ONLY_SIGNED' | translate}}</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
<div class="clr-form-control mt-0" *ngIf="withNotary() && enableContentTrust && !onlySignedImages">
|
||||||
|
<label for="repo" class="width-6rem"></label>
|
||||||
|
<div class="clr-control-container">
|
||||||
|
<label class="label label-warning warning">
|
||||||
|
<span>{{'P2P_PROVIDER.CONTENT_WARNING' | translate}}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clr-form-control margin-top-06" [ngClass]="{'mt-1': !withNotary()}">
|
||||||
|
<label for="repo" class="clr-control-label width-6rem">
|
||||||
|
<span *ngIf="!withNotary()">{{'P2P_PROVIDER.CRITERIA' | translate}}</span>
|
||||||
|
</label>
|
||||||
|
<div class="clr-control-container flex">
|
||||||
|
<label class="sub-text">{{'P2P_PROVIDER.START_TEXT' | translate}} </label>
|
||||||
|
<div class="clr-select-wrapper ">
|
||||||
|
<select id="severity" name="severity" class="clr-select"
|
||||||
|
#ngSeverity="ngModel"
|
||||||
|
[(ngModel)]="severity">
|
||||||
|
<option class="display-none" value=""></option>
|
||||||
|
<option [selected]="severity == s.severity" *ngFor='let s of severityOptions' value="{{s.severity}}">
|
||||||
|
{{ s.severityLevel | translate }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="sub-text"> {{'P2P_PROVIDER.EDN_TEXT' | translate}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clr-form-control mt-0" *ngIf="compare()">
|
||||||
|
<label for="repo" class="width-6rem"></label>
|
||||||
|
<div class="clr-control-container">
|
||||||
|
<label class="label label-warning warning">
|
||||||
|
<span>{{'P2P_PROVIDER.SEVERITY_WARNING' | translate}}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clr-form-control margin-top-06">
|
||||||
|
<label for="repo" class="clr-control-label width-6rem"></label>
|
||||||
|
<div class="clr-control-container">
|
||||||
|
<div class="clr-input-wrapper">
|
||||||
|
<label class="sub-label">{{'P2P_PROVIDER.LABELS' | translate}}</label>
|
||||||
|
<input [disabled]="loading" autocomplete="off" class="clr-input width-290" type="text" id="labels" [(ngModel)]="labels"
|
||||||
|
size="30" name="label">
|
||||||
|
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<clr-select-container>
|
||||||
|
<label class="clr-control-label width-6rem">{{'P2P_PROVIDER.TRIGGER' | translate}}</label>
|
||||||
|
<select class="width-380" [disabled]="loading"
|
||||||
|
clrSelect
|
||||||
|
name="triggerType" id="trigger-type"
|
||||||
|
#ngTriggerType="ngModel"
|
||||||
|
[(ngModel)]="triggerType"
|
||||||
|
>
|
||||||
|
<option class="display-none" value=""></option>
|
||||||
|
<option [selected]="triggerType == item" *ngFor="let item of triggers" value="{{item}}">{{getTriggerTypeI18n(item)| translate}}</option>
|
||||||
|
</select>
|
||||||
|
</clr-select-container>
|
||||||
|
<div class="clr-form-control margin-top-06" *ngIf="showCron()">
|
||||||
|
<label for="repo" class="clr-control-label width-6rem"></label>
|
||||||
|
<div class="clr-control-container">
|
||||||
|
<cron-selection #cronScheduleComponent [isInlineModel]="true" [originCron]='getCron()' (inputvalue)="setCron($event)"></cron-selection>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-modal>
|
@ -0,0 +1,73 @@
|
|||||||
|
@mixin cus-font {
|
||||||
|
font-size: .541667rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host::ng-deep.modal-dialog {
|
||||||
|
width: 27rem;
|
||||||
|
}
|
||||||
|
.display-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.icon-tooltip {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.padding-top-3 {
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
.bottom-btn {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.width-380 {
|
||||||
|
width: 380px;
|
||||||
|
}
|
||||||
|
.width-290 {
|
||||||
|
width: 290px
|
||||||
|
}
|
||||||
|
.width-6rem {
|
||||||
|
width: 6rem!important;
|
||||||
|
}
|
||||||
|
.clr-control-label {
|
||||||
|
width: 8rem;
|
||||||
|
}
|
||||||
|
.sub-label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 90px;
|
||||||
|
@include cus-font;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-text {
|
||||||
|
@include cus-font;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.margin-top-06 {
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-left-90px{
|
||||||
|
margin-left: 90px;
|
||||||
|
}
|
||||||
|
.opacity-08 {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.space-between {
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.go-link {
|
||||||
|
line-height: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.alert-label {
|
||||||
|
color:#f9392e;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
width: 320px;
|
||||||
|
line-height: 0.5rem;
|
||||||
|
white-space: normal;
|
||||||
|
height: auto;
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ClarityModule } from '@clr/angular';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { AddP2pPolicyComponent } from './add-p2p-policy.component';
|
||||||
|
import { P2pProviderService } from '../p2p-provider.service';
|
||||||
|
import { ErrorHandler } from '../../../../lib/utils/error-handler';
|
||||||
|
import { PreheatService } from '../../../../../ng-swagger-gen/services/preheat.service';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { SessionService } from '../../../shared/session.service';
|
||||||
|
import { AppConfigService } from '../../../services/app-config.service';
|
||||||
|
import { InlineAlertComponent } from '../../../shared/inline-alert/inline-alert.component';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { delay } from 'rxjs/operators';
|
||||||
|
describe('AddP2pPolicyComponent', () => {
|
||||||
|
let component: AddP2pPolicyComponent;
|
||||||
|
let fixture: ComponentFixture<AddP2pPolicyComponent>;
|
||||||
|
const mockedAppConfigService = {
|
||||||
|
getConfig() {
|
||||||
|
return {
|
||||||
|
with_notary: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockPreheatService = {
|
||||||
|
CreatePolicy() {
|
||||||
|
return of(true).pipe(delay(0));
|
||||||
|
},
|
||||||
|
UpdatePolicy() {
|
||||||
|
return of(true).pipe(delay(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockActivatedRoute = {
|
||||||
|
snapshot: {
|
||||||
|
parent: {
|
||||||
|
parent: {
|
||||||
|
params: { id: 1 },
|
||||||
|
data: {
|
||||||
|
projectResolver: {
|
||||||
|
name: 'library',
|
||||||
|
metadata: {
|
||||||
|
prevent_vul: 'true',
|
||||||
|
enable_content_trust: 'true',
|
||||||
|
severity: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockedSessionService = {
|
||||||
|
getCurrentUser() {
|
||||||
|
return {
|
||||||
|
has_admin_role: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA,
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
ClarityModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
FormsModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
|
HttpClientTestingModule
|
||||||
|
],
|
||||||
|
declarations: [AddP2pPolicyComponent, InlineAlertComponent],
|
||||||
|
providers: [
|
||||||
|
P2pProviderService,
|
||||||
|
ErrorHandler,
|
||||||
|
{ provide: PreheatService, useValue: mockPreheatService },
|
||||||
|
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||||
|
{ provide: SessionService, useValue: mockedSessionService },
|
||||||
|
{ provide: AppConfigService, useValue: mockedAppConfigService },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AddP2pPolicyComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should open and close modal', async () => {
|
||||||
|
await fixture.whenStable();
|
||||||
|
component.isOpen = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
let modalBody: HTMLDivElement = fixture.nativeElement.querySelector('.modal-body');
|
||||||
|
expect(modalBody).toBeTruthy();
|
||||||
|
component.closeModal();
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
modalBody = fixture.nativeElement.querySelector('.modal-body');
|
||||||
|
expect(modalBody).toBeFalsy();
|
||||||
|
});
|
||||||
|
it("should show a 'name is required' error", async () => {
|
||||||
|
fixture.autoDetectChanges(true);
|
||||||
|
component.isOpen = true;
|
||||||
|
await fixture.whenStable();
|
||||||
|
const nameInput: HTMLInputElement = fixture.nativeElement.querySelector("#name");
|
||||||
|
nameInput.value = "test";
|
||||||
|
nameInput.dispatchEvent(new Event('input'));
|
||||||
|
nameInput.value = null;
|
||||||
|
nameInput.dispatchEvent(new Event('input'));
|
||||||
|
nameInput.blur();
|
||||||
|
const errorEle: HTMLElement = fixture.nativeElement.querySelector("clr-control-error");
|
||||||
|
expect(errorEle.innerText).toEqual('P2P_PROVIDER.NAME_REQUIRED');
|
||||||
|
});
|
||||||
|
it("save button should work", async () => {
|
||||||
|
fixture.autoDetectChanges(true);
|
||||||
|
component.isOpen = true;
|
||||||
|
await fixture.whenStable();
|
||||||
|
const spy: jasmine.Spy = spyOn(component, 'addOrSave').and.returnValue(undefined);
|
||||||
|
component.tags = "**";
|
||||||
|
component.repos = "**";
|
||||||
|
component.policy = {
|
||||||
|
provider_id: 1
|
||||||
|
};
|
||||||
|
const nameInput: HTMLInputElement = fixture.nativeElement.querySelector("#name");
|
||||||
|
nameInput.value = "policy1";
|
||||||
|
nameInput.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
expect(component.valid()).toBeTruthy();
|
||||||
|
const addButton: HTMLButtonElement = fixture.nativeElement.querySelector("#new-policy");
|
||||||
|
addButton.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
expect(spy.calls.count()).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,297 @@
|
|||||||
|
import { Component, EventEmitter, Input, 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 } 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 { deleteEmptyKey } from '../../../../lib/utils/utils';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { SessionService } from '../../../shared/session.service';
|
||||||
|
import { Project } from '../../project';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const SCHEDULE_TYPE = {
|
||||||
|
NONE: "None",
|
||||||
|
DAILY: "Daily",
|
||||||
|
WEEKLY: "Weekly",
|
||||||
|
HOURLY: "Hourly",
|
||||||
|
CUSTOM: "Custom"
|
||||||
|
};
|
||||||
|
const TRUE: string = 'true';
|
||||||
|
@Component({
|
||||||
|
selector: 'add-p2p-policy',
|
||||||
|
templateUrl: './add-p2p-policy.component.html',
|
||||||
|
styleUrls: ['./add-p2p-policy.component.scss']
|
||||||
|
})
|
||||||
|
export class AddP2pPolicyComponent implements OnInit {
|
||||||
|
severityOptions = [
|
||||||
|
{severity: 5, severityLevel: 'VULNERABILITY.SEVERITY.CRITICAL'},
|
||||||
|
{severity: 4, severityLevel: 'VULNERABILITY.SEVERITY.HIGH'},
|
||||||
|
{severity: 3, severityLevel: 'VULNERABILITY.SEVERITY.MEDIUM'},
|
||||||
|
{severity: 2, severityLevel: 'VULNERABILITY.SEVERITY.LOW'},
|
||||||
|
{severity: 0, severityLevel: 'VULNERABILITY.SEVERITY.NONE'},
|
||||||
|
];
|
||||||
|
isEdit: boolean;
|
||||||
|
isOpen: boolean = false;
|
||||||
|
closable: boolean = false;
|
||||||
|
staticBackdrop: boolean = true;
|
||||||
|
projectName: string;
|
||||||
|
projectId: number;
|
||||||
|
@Output() notify = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
@ViewChild(InlineAlertComponent, { static: false } )
|
||||||
|
inlineAlert: InlineAlertComponent;
|
||||||
|
policy: PreheatPolicy = {};
|
||||||
|
repos: string;
|
||||||
|
tags: string;
|
||||||
|
onlySignedImages: boolean = false;
|
||||||
|
severity: number = 0;
|
||||||
|
labels: string;
|
||||||
|
triggerType: string = TRIGGER.MANUAL;
|
||||||
|
cron: string ;
|
||||||
|
@ViewChild("policyForm", { static: true }) currentForm: NgForm;
|
||||||
|
loading: boolean = false;
|
||||||
|
@ViewChild('cronScheduleComponent', {static: false})
|
||||||
|
cronScheduleComponent: CronScheduleComponent;
|
||||||
|
buttonStatus: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
originPolicyForEdit: PreheatPolicy;
|
||||||
|
originReposForEdit: string;
|
||||||
|
originTagsForEdit: string;
|
||||||
|
originOnlySignedImagesForEdit: boolean;
|
||||||
|
originSeverityForEdit: number;
|
||||||
|
originLabelsForEdit: string;
|
||||||
|
originTriggerTypeForEdit: string;
|
||||||
|
originCronForEdit: string;
|
||||||
|
@Input()
|
||||||
|
providers: ProviderUnderProject[] = [];
|
||||||
|
preventVul: boolean = false;
|
||||||
|
projectSeverity: string;
|
||||||
|
triggers: string[] = [TRIGGER.MANUAL, TRIGGER.SCHEDULED, TRIGGER.EVENT_BASED];
|
||||||
|
enableContentTrust: boolean = false;
|
||||||
|
|
||||||
|
constructor(private preheatService: PreheatService,
|
||||||
|
private session: SessionService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private appConfigService: AppConfigService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const resolverData = this.route.snapshot.parent.parent.data;
|
||||||
|
if (resolverData) {
|
||||||
|
const project = <Project>(resolverData["projectResolver"]);
|
||||||
|
this.projectName = project.name;
|
||||||
|
this.projectId = project.project_id;
|
||||||
|
if (project && project.metadata) {
|
||||||
|
this.preventVul = project.metadata.prevent_vul === TRUE;
|
||||||
|
this.projectSeverity = project.metadata.severity;
|
||||||
|
this.enableContentTrust = project.metadata.enable_content_trust === TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForAdd() {
|
||||||
|
this.currentForm.reset({
|
||||||
|
severity: 0,
|
||||||
|
triggerType: "manual"
|
||||||
|
});
|
||||||
|
this.inlineAlert.close();
|
||||||
|
this.policy = {};
|
||||||
|
this.repos = null;
|
||||||
|
this.tags = null;
|
||||||
|
this.onlySignedImages = false;
|
||||||
|
this.severity = 0;
|
||||||
|
this.labels = null;
|
||||||
|
this.triggerType = "manual";
|
||||||
|
this.cron = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCron(event: any) {
|
||||||
|
this.cron = event;
|
||||||
|
this.cronScheduleComponent.resetSchedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCron(): OriginCron {
|
||||||
|
const originCron: OriginCron = {
|
||||||
|
type: SCHEDULE_TYPE.NONE,
|
||||||
|
cron: ''
|
||||||
|
};
|
||||||
|
originCron.cron = this.cron;
|
||||||
|
if (originCron.cron === '' || originCron.cron === null || originCron.cron === undefined) {
|
||||||
|
originCron.type = SCHEDULE_TYPE.NONE;
|
||||||
|
} else if (originCron.cron === '0 0 * * * *') {
|
||||||
|
originCron.type = SCHEDULE_TYPE.HOURLY;
|
||||||
|
} else if (originCron.cron === '0 0 0 * * *') {
|
||||||
|
originCron.type = SCHEDULE_TYPE.DAILY;
|
||||||
|
} else if (originCron.cron === '0 0 0 * * 0') {
|
||||||
|
originCron.type = SCHEDULE_TYPE.WEEKLY;
|
||||||
|
} else {
|
||||||
|
originCron.type = SCHEDULE_TYPE.CUSTOM;
|
||||||
|
}
|
||||||
|
return originCron;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel() {
|
||||||
|
this.isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
this.isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrSave(isAdd: boolean) {
|
||||||
|
const policy: PreheatPolicy = {};
|
||||||
|
Object.assign(policy, this.policy);
|
||||||
|
policy.provider_id = +policy.provider_id;
|
||||||
|
const filters: any[] = [];
|
||||||
|
if (this.repos) {
|
||||||
|
if (this.repos.indexOf(",") !== -1) {
|
||||||
|
filters.push({type: FILTER_TYPE.REPOS, value: `{${this.repos}}`});
|
||||||
|
} else {
|
||||||
|
filters.push({type: FILTER_TYPE.REPOS, value: this.repos});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.tags) {
|
||||||
|
if (this.tags.indexOf(",") !== -1) {
|
||||||
|
filters.push({type: FILTER_TYPE.TAG, value: `{${this.tags}}`});
|
||||||
|
} else {
|
||||||
|
filters.push({type: FILTER_TYPE.TAG, value: this.tags});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.onlySignedImages) {
|
||||||
|
filters.push({type: FILTER_TYPE.SIGNATURE, value: this.onlySignedImages});
|
||||||
|
}
|
||||||
|
if (this.labels) {
|
||||||
|
if (this.labels.indexOf(",") !== -1) {
|
||||||
|
filters.push({type: FILTER_TYPE.LABEL, value: `{${this.labels}}`});
|
||||||
|
} else {
|
||||||
|
filters.push({type: FILTER_TYPE.LABEL, value: this.labels});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.severity === 0 || this.severity > 0) {
|
||||||
|
filters.push({type: FILTER_TYPE.VULNERABILITY, value: this.severity});
|
||||||
|
}
|
||||||
|
policy.filters = JSON.stringify(filters);
|
||||||
|
const trigger: any = {
|
||||||
|
type: this.triggerType ? this.triggerType : TRIGGER.MANUAL,
|
||||||
|
trigger_setting: {
|
||||||
|
cron: (!this.triggerType
|
||||||
|
|| this.triggerType === TRIGGER.MANUAL
|
||||||
|
|| this.triggerType === TRIGGER.EVENT_BASED) ? "" : this.cron
|
||||||
|
}
|
||||||
|
};
|
||||||
|
policy.trigger = JSON.stringify(trigger);
|
||||||
|
this.loading = true;
|
||||||
|
this.buttonStatus = ClrLoadingState.LOADING;
|
||||||
|
deleteEmptyKey(policy);
|
||||||
|
if (isAdd) {
|
||||||
|
policy.project_id = this.projectId;
|
||||||
|
policy.enabled = true;
|
||||||
|
this.preheatService.CreatePolicy({projectName: this.projectName,
|
||||||
|
policy: policy
|
||||||
|
}).pipe(finalize(() => this.loading = false))
|
||||||
|
.subscribe(response => {
|
||||||
|
this.buttonStatus = ClrLoadingState.SUCCESS;
|
||||||
|
this.closeModal();
|
||||||
|
this.notify.emit(isAdd);
|
||||||
|
}, error => {
|
||||||
|
this.inlineAlert.showInlineError(error);
|
||||||
|
this.buttonStatus = ClrLoadingState.ERROR;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
policy.id = this.originPolicyForEdit.id;
|
||||||
|
this.preheatService.UpdatePolicy({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.originPolicyForEdit.name,
|
||||||
|
policy: policy
|
||||||
|
}).pipe(finalize(() => this.loading = false))
|
||||||
|
.subscribe(response => {
|
||||||
|
this.buttonStatus = ClrLoadingState.SUCCESS;
|
||||||
|
this.closeModal();
|
||||||
|
this.notify.emit(isAdd);
|
||||||
|
}, error => {
|
||||||
|
this.inlineAlert.showInlineError(error);
|
||||||
|
this.buttonStatus = ClrLoadingState.ERROR;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
valid(): boolean {
|
||||||
|
return this.currentForm.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
compare(): boolean {
|
||||||
|
if (this.projectSeverity && this.preventVul) {
|
||||||
|
if (PROJECT_SEVERITY_LEVEL_MAP[this.projectSeverity] > (this.severity ? this.severity : 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChange(): boolean {
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if (this.policy.provider_id != this.originPolicyForEdit.provider_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if (this.policy.name != this.originPolicyForEdit.name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( (this.policy.description || this.originPolicyForEdit.description)
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
&& this.policy.description != this.originPolicyForEdit.description) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if (this.originReposForEdit != this.repos) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if (this.originTagsForEdit != this.tags) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if (this.originOnlySignedImagesForEdit != this.onlySignedImages) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if (this.originLabelsForEdit != this.labels) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if (this.originSeverityForEdit != this.severity) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
if (this.originTriggerTypeForEdit != this.triggerType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:triple-equals
|
||||||
|
return this.originCronForEdit != this.cron;
|
||||||
|
}
|
||||||
|
isSystemAdmin(): boolean {
|
||||||
|
const account = this.session.getCurrentUser();
|
||||||
|
return account != null && account.has_admin_role;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTriggerTypeI18n(triggerType): string {
|
||||||
|
if (triggerType) {
|
||||||
|
return TRIGGER_I18N_MAP[triggerType];
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
showCron(): boolean {
|
||||||
|
if (this.triggerType) {
|
||||||
|
return this.triggerType === TRIGGER.SCHEDULED;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
withNotary(): boolean {
|
||||||
|
return this.appConfigService.getConfig().with_notary;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
<router-outlet></router-outlet>
|
@ -0,0 +1,28 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { P2pProviderComponent } from './p2p-provider.component';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
describe('P2pProviderComponent', () => {
|
||||||
|
let component: P2pProviderComponent;
|
||||||
|
let fixture: ComponentFixture<P2pProviderComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ P2pProviderComponent ],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(P2pProviderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'p2p-provider',
|
||||||
|
templateUrl: './p2p-provider.component.html',
|
||||||
|
styleUrls: ['./p2p-provider.component.scss']
|
||||||
|
})
|
||||||
|
export class P2pProviderComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
import { EXECUTION_STATUS, P2pProviderService } from './p2p-provider.service';
|
||||||
|
|
||||||
|
describe('P2pProviderService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [P2pProviderService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([P2pProviderService], (service: P2pProviderService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
it('function getDuration should work', inject([P2pProviderService], (service: P2pProviderService) => {
|
||||||
|
const date = new Date();
|
||||||
|
const noDuration: string = service.getDuration(new Date(date).toUTCString(), new Date(date.getTime()).toUTCString());
|
||||||
|
expect(noDuration).toEqual('-');
|
||||||
|
const durationMs: string = service.getDuration(new Date(date).toUTCString(), new Date(date.getTime() + 10).toUTCString());
|
||||||
|
expect(durationMs).toEqual('-');
|
||||||
|
const durationSec: string = service.getDuration(new Date(date).toUTCString(), new Date(date.getTime() + 1010).toUTCString());
|
||||||
|
expect(durationSec).toEqual('1s');
|
||||||
|
const durationMin: string = service.getDuration(new Date(date).toUTCString(), new Date(date.getTime() + 61010).toUTCString());
|
||||||
|
expect(durationMin).toEqual('1m1s');
|
||||||
|
const durationMinOnly: string = service.getDuration(new Date(date).toUTCString(), new Date(date.getTime() + 60000).toUTCString());
|
||||||
|
expect(durationMinOnly).toEqual('1m');
|
||||||
|
}));
|
||||||
|
it('function willChangStatus should work', inject([P2pProviderService], (service: P2pProviderService) => {
|
||||||
|
expect(service.willChangStatus(EXECUTION_STATUS.PENDING)).toBeTruthy();
|
||||||
|
expect(service.willChangStatus(EXECUTION_STATUS.RUNNING)).toBeTruthy();
|
||||||
|
expect(service.willChangStatus(EXECUTION_STATUS.SCHEDULED)).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
103
src/portal/src/app/project/p2p-provider/p2p-provider.service.ts
Normal file
103
src/portal/src/app/project/p2p-provider/p2p-provider.service.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// 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 { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
const ONE_MINUTE_SECONDS: number = 60;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class P2pProviderService {
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
getDuration(start: string, end: string): string {
|
||||||
|
if (!start || !end) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
let startTime = new Date(start).getTime();
|
||||||
|
let endTime = new Date(end).getTime();
|
||||||
|
let timesDiff = endTime - startTime;
|
||||||
|
let timesDiffSeconds = timesDiff / 1000;
|
||||||
|
let minutes = Math.floor(timesDiffSeconds / ONE_MINUTE_SECONDS);
|
||||||
|
let seconds = Math.floor(timesDiffSeconds % ONE_MINUTE_SECONDS);
|
||||||
|
if (minutes > 0) {
|
||||||
|
if (seconds === 0) {
|
||||||
|
return minutes + "m";
|
||||||
|
}
|
||||||
|
return minutes + "m" + seconds + "s";
|
||||||
|
}
|
||||||
|
if (seconds > 0) {
|
||||||
|
return seconds + "s";
|
||||||
|
}
|
||||||
|
if (seconds <= 0 && timesDiff > 0) {
|
||||||
|
return timesDiff + 'ms';
|
||||||
|
} else {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
willChangStatus(status: string): boolean {
|
||||||
|
return status === EXECUTION_STATUS.PENDING
|
||||||
|
|| status === EXECUTION_STATUS.RUNNING
|
||||||
|
|| status === EXECUTION_STATUS.SCHEDULED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EXECUTION_STATUS {
|
||||||
|
PENDING = 'Pending',
|
||||||
|
RUNNING = 'Running',
|
||||||
|
STOPPED = 'Stopped',
|
||||||
|
ERROR = 'Error',
|
||||||
|
SUCCESS = 'Success',
|
||||||
|
SCHEDULED = 'Scheduled'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TRIGGER {
|
||||||
|
MANUAL = 'manual',
|
||||||
|
SCHEDULED = 'scheduled',
|
||||||
|
EVENT_BASED = 'event_based'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TRIGGER_I18N_MAP = {
|
||||||
|
'manual': 'P2P_PROVIDER.MANUAL',
|
||||||
|
'scheduled': 'P2P_PROVIDER.SCHEDULED',
|
||||||
|
'event_based': 'P2P_PROVIDER.EVENT_BASED'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TIME_OUT: number = 7000;
|
||||||
|
|
||||||
|
export const PROJECT_SEVERITY_LEVEL_MAP = {
|
||||||
|
"critical": 5,
|
||||||
|
"high": 4,
|
||||||
|
"medium": 3,
|
||||||
|
"low": 2,
|
||||||
|
"negligible": 1,
|
||||||
|
"unknown": 0,
|
||||||
|
"none": 0
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PROJECT_SEVERITY_LEVEL_TO_TEXT_MAP = {
|
||||||
|
5: "critical",
|
||||||
|
4: "high",
|
||||||
|
3: "medium",
|
||||||
|
2: "low",
|
||||||
|
1: "negligible",
|
||||||
|
0: "none",
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum FILTER_TYPE {
|
||||||
|
REPOS = 'repository',
|
||||||
|
TAG = 'tag',
|
||||||
|
SIGNATURE = 'signature',
|
||||||
|
LABEL = 'label',
|
||||||
|
VULNERABILITY = 'vulnerability'
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
<div class="row">
|
||||||
|
<h4 class="mt-1">{{'P2P_PROVIDER.POLICIES' | translate}}</h4>
|
||||||
|
<clr-datagrid (clrDgSingleSelectedChange)="refreshJobs($event)" [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow">
|
||||||
|
<clr-dg-action-bar>
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col-7">
|
||||||
|
<button (click)="newPolicy()" [disabled]="!hasCreatPermission" [clrLoading]="addBtnState"
|
||||||
|
id="new-policy" type="button" class="btn btn-secondary">
|
||||||
|
<clr-icon shape="plus" size="16"></clr-icon>
|
||||||
|
{{'P2P_PROVIDER.NEW_POLICY' | translate}}
|
||||||
|
</button>
|
||||||
|
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="btn btn-link" clrDropdownTrigger>
|
||||||
|
<span id="action-policy">{{'MEMBER.ACTION' | translate}}
|
||||||
|
<clr-icon class="clr-icon" shape="caret down"></clr-icon></span>
|
||||||
|
<clr-dropdown-menu *clrIfOpen>
|
||||||
|
<button clrDropdownItem
|
||||||
|
class="btn btn-secondary"
|
||||||
|
[disabled]="!(selectedRow && selectedRow.enabled) || !hasUpdatePermission || executing"
|
||||||
|
(click)="executePolicy()">
|
||||||
|
<span>
|
||||||
|
<clr-icon class="margin-top-0" size="16" shape="play"></clr-icon>
|
||||||
|
<span id="execute-policy"
|
||||||
|
class="margin-left-5px">{{'P2P_PROVIDER.EXECUTE' | translate}}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" clrDropdownItem (click)="switchStatus()"
|
||||||
|
[disabled]="!selectedRow || !hasUpdatePermission">
|
||||||
|
<span id="toggle-policy">
|
||||||
|
<span *ngIf="selectedRow && !selectedRow.enabled">
|
||||||
|
<clr-icon class="margin-top-2" size="16" shape="success-standard"></clr-icon>
|
||||||
|
<span class="margin-left-5px">{{'WEBHOOK.ENABLED_BUTTON' | translate}}</span>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="!(selectedRow && !selectedRow.enabled)">
|
||||||
|
<clr-icon class="margin-top-2" size="16" shape="ban"></clr-icon>
|
||||||
|
<span class="margin-left-5px">{{'WEBHOOK.DISABLED_BUTTON' | translate}}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button clrDropdownItem
|
||||||
|
class="btn btn-secondary"
|
||||||
|
[disabled]="!selectedRow || !hasUpdatePermission"
|
||||||
|
(click)="editPolicy()">
|
||||||
|
<span>
|
||||||
|
<clr-icon class="margin-top-0" size="16" shape="pencil"></clr-icon>
|
||||||
|
<span id="edit-policy" class="margin-left-5px">{{'BUTTON.EDIT' | translate}}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<button clrDropdownItem (click)="deletePolicy()"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
[disabled]="!selectedRow || !hasDeletePermission">
|
||||||
|
<span>
|
||||||
|
<clr-icon class="margin-top-0" size="16" shape="times"></clr-icon>
|
||||||
|
<span id="delete-policy"
|
||||||
|
class="margin-left-5px">{{'BUTTON.DELETE' | translate}}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</clr-dropdown-menu>
|
||||||
|
</clr-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="clr-col-5">
|
||||||
|
<div class="action-head-pos">
|
||||||
|
<span class="refresh-btn">
|
||||||
|
<clr-icon shape="refresh" (click)="refresh()" [hidden]="loading"></clr-icon>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>{{'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>
|
||||||
|
<clr-dg-column>{{'P2P_PROVIDER.DESCRIPTION' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-placeholder>
|
||||||
|
{{'P2P_PROVIDER.NO_POLICY' | translate}}
|
||||||
|
</clr-dg-placeholder>
|
||||||
|
<clr-dg-row *clrDgItems="let p of policyList" [clrDgItem]="p">
|
||||||
|
<clr-dg-cell>{{p.name}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<div *ngIf="p.enabled" class="icon-wrap">
|
||||||
|
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
|
||||||
|
<span class="margin-left-5px">{{'WEBHOOK.ENABLED' | translate}}</span>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!p.enabled" class="icon-wrap">
|
||||||
|
<clr-icon shape="exclamation-triangle" size="20" class="is-warning"></clr-icon>
|
||||||
|
<span class="margin-left-5px">{{'WEBHOOK.DISABLED' | translate}}</span>
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{p.provider_name}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<div>
|
||||||
|
<span *ngIf="getValue(p.filters, 'repository')">
|
||||||
|
<span class="filter-title">{{'P2P_PROVIDER.REPOS' | translate}}</span>
|
||||||
|
<span class="opacity08">: {{getValue(p.filters, 'repository')}}</span>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="getValue(p.filters, 'tag')"
|
||||||
|
class="margin-left-10px">
|
||||||
|
<span class="filter-title">{{'P2P_PROVIDER.TAGS' | translate}}</span>
|
||||||
|
<span class="opacity08">: {{getValue(p.filters, 'tag')}}</span>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="getValue(p.filters, 'label')"
|
||||||
|
class="margin-left-10px">
|
||||||
|
<span class="filter-title">{{'P2P_PROVIDER.LABELS' | translate}}</span>
|
||||||
|
<span class="opacity08">: {{getValue(p.filters, 'label')}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="margin-top-5px" *ngIf="getValue(p.filters, 'signature')">
|
||||||
|
<span class="filter-title">{{'P2P_PROVIDER.ONLY_SIGNED' | translate}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="margin-top-5px" *ngIf="getValue(p.filters, 'vulnerability')">
|
||||||
|
<span>{{'P2P_PROVIDER.START_TEXT' | translate}} </span>
|
||||||
|
<span class="opacity08">{{severity_map[getValue(p.filters, 'vulnerability')]}}</span>
|
||||||
|
<span> {{'P2P_PROVIDER.EDN_TEXT' | translate}}</span>
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
{{getTriggerTypeI18n(p.trigger) | translate}}
|
||||||
|
<clr-signpost *ngIf="isScheduled(p.trigger)">
|
||||||
|
<clr-signpost-content *clrIfOpen>
|
||||||
|
{{getScheduledCron(p.trigger)}}
|
||||||
|
</clr-signpost-content>
|
||||||
|
</clr-signpost>
|
||||||
|
<clr-signpost *ngIf="isEventBased(p.trigger)">
|
||||||
|
<clr-signpost-content *clrIfOpen>
|
||||||
|
<div>{{'P2P_PROVIDER.EVENT_BASED_EXPLAIN_LINE1' | translate}}</div>
|
||||||
|
<div>{{'P2P_PROVIDER.EVENT_BASED_EXPLAIN_LINE2' | translate}}</div>
|
||||||
|
<div>{{'P2P_PROVIDER.EVENT_BASED_EXPLAIN_LINE3' | translate}}</div>
|
||||||
|
<div>{{'P2P_PROVIDER.EVENT_BASED_EXPLAIN_LINE4' | translate}}</div>
|
||||||
|
</clr-signpost-content>
|
||||||
|
</clr-signpost>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{p.creation_time | date: 'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{p.description}}</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
<clr-dg-footer>
|
||||||
|
<span *ngIf="policyList?.length > 0">1 - {{policyList?.length}} {{'WEBHOOK.OF' | translate}} </span> {{policyList?.length}} {{'WEBHOOK.ITEMS' | translate}}
|
||||||
|
<clr-dg-pagination [clrDgPageSize]="10"></clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 " *ngIf="selectedRow">
|
||||||
|
<h4 class="mt-2">{{'P2P_PROVIDER.EXECUTIONS' | translate}}</h4>
|
||||||
|
<clr-datagrid [(clrDgSingleSelected)]="selectedExecutionRow" [clrDgLoading]="jobsLoading"
|
||||||
|
(clrDgRefresh)="clrLoadJobs(null,true)">
|
||||||
|
<clr-dg-action-bar>
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col-7">
|
||||||
|
<button type="button" class="btn btn-secondary" [disabled]="!hasUpdatePermission || !selectedExecutionRow || jobsLoading || stopLoading || !canStop()"
|
||||||
|
(click)="openStopExecutionsDialog()">{{'REPLICATION.STOPJOB' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
<div class="clr-col-5 flex-end">
|
||||||
|
<div class="select filter-tag clr-select-wrapper" [hidden]="!isOpenFilterTag">
|
||||||
|
<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>
|
||||||
|
</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>{{'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>
|
||||||
|
<clr-dg-column>{{'P2P_PROVIDER.PROVIDER_TYPE' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-placeholder>{{'P2P_PROVIDER.JOB_PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||||
|
<clr-dg-row *ngFor="let execution of executionList" [clrDgItem]="execution">
|
||||||
|
<clr-dg-cell>
|
||||||
|
<a href="javascript:void(0)" (click)="goToLink(execution.id)">{{execution.id}}</a>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
{{execution.status}}
|
||||||
|
<clr-tooltip>
|
||||||
|
<clr-icon *ngIf="execution.status_message" clrTooltipTrigger shape="info-circle" size="20"></clr-icon>
|
||||||
|
<clr-tooltip-content [clrPosition]="'left'" clrSize="md" *clrIfOpen>
|
||||||
|
<span>{{execution.status_message}}</span>
|
||||||
|
</clr-tooltip-content>
|
||||||
|
</clr-tooltip>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{execution.trigger}}</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>
|
||||||
|
<clr-dg-cell>{{execution.vendor_type}}</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
<clr-dg-footer>
|
||||||
|
<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"
|
||||||
|
[clrDgTotalItems]="totalExecutionCount"></clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
<add-p2p-policy [providers]="providers" (notify)="success($event)"></add-p2p-policy>
|
||||||
|
<confirmation-dialog (confirmAction)="confirmSwitch($event)" #confirmationDialogComponent></confirmation-dialog>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
.action-head-pos {
|
||||||
|
padding-right: 18px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.icon-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.margin-left-5px{
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.margin-left-10px{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.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: 6px;
|
||||||
|
}
|
||||||
|
.filter-title {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.margin-top-5px {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.opacity08 {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
@ -0,0 +1,191 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ClarityModule } from '@clr/angular';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { delay } from "rxjs/operators";
|
||||||
|
import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component";
|
||||||
|
import { ConfirmationDialogComponent } from "../../../../lib/components/confirmation-dialog";
|
||||||
|
import { UserPermissionService } from '../../../../lib/services';
|
||||||
|
import { PolicyComponent } from './policy.component';
|
||||||
|
import { PreheatService } from '../../../../../ng-swagger-gen/services/preheat.service';
|
||||||
|
import { AddP2pPolicyComponent } from '../add-p2p-policy/add-p2p-policy.component';
|
||||||
|
import { PreheatPolicy } from '../../../../../ng-swagger-gen/models/preheat-policy';
|
||||||
|
import { Execution } from '../../../../../ng-swagger-gen/models/execution';
|
||||||
|
import { ProviderUnderProject } from '../../../../../ng-swagger-gen/models/provider-under-project';
|
||||||
|
import { P2pProviderService } from '../p2p-provider.service';
|
||||||
|
import { SessionService } from '../../../shared/session.service';
|
||||||
|
import { AppConfigService } from '../../../services/app-config.service';
|
||||||
|
import { ErrorHandler } from '../../../../lib/utils/error-handler';
|
||||||
|
|
||||||
|
describe('PolicyComponent', () => {
|
||||||
|
let component: PolicyComponent;
|
||||||
|
let fixture: ComponentFixture<PolicyComponent>;
|
||||||
|
const mockMessageHandlerService = {
|
||||||
|
handleError: () => { }
|
||||||
|
};
|
||||||
|
const providers: ProviderUnderProject[] = [{
|
||||||
|
id: 1,
|
||||||
|
provider: 'Kraken'
|
||||||
|
}];
|
||||||
|
const mockActivatedRoute = {
|
||||||
|
snapshot: {
|
||||||
|
parent: {
|
||||||
|
parent: {
|
||||||
|
params: { id: 1 },
|
||||||
|
data: {
|
||||||
|
projectResolver: {
|
||||||
|
name: 'library',
|
||||||
|
metadata: {
|
||||||
|
prevent_vul: 'true',
|
||||||
|
enable_content_trust: 'true',
|
||||||
|
severity: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockUserPermissionService = {
|
||||||
|
getPermission() {
|
||||||
|
return of(true).pipe(delay(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockedSessionService = {
|
||||||
|
getCurrentUser() {
|
||||||
|
return {
|
||||||
|
has_admin_role: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const policy1: PreheatPolicy = {
|
||||||
|
id: 1,
|
||||||
|
name: 'policy1',
|
||||||
|
provider_id: 1,
|
||||||
|
enabled: true,
|
||||||
|
filters: '[{"type":"repository","value":"**"},{"type":"tag","value":"**"},{"type":"vulnerability","value":2}]',
|
||||||
|
trigger: '{"type":"manual","trigger_setting":{"cron":""}}',
|
||||||
|
creation_time: '2020-01-02T15:04:05',
|
||||||
|
description: 'policy1'
|
||||||
|
};
|
||||||
|
|
||||||
|
const policy2: PreheatPolicy = {
|
||||||
|
id: 2,
|
||||||
|
name: 'policy2',
|
||||||
|
provider_id: 2,
|
||||||
|
enabled: false,
|
||||||
|
filters: '[{"type":"repository","value":"**"},{"type":"tag","value":"**"},{"type":"vulnerability","value":2}]',
|
||||||
|
trigger: '{"type":"manual","trigger_setting":{"cron":""}}',
|
||||||
|
creation_time: '2020-01-02T15:04:05',
|
||||||
|
description: 'policy2'
|
||||||
|
};
|
||||||
|
const execution: Execution = {
|
||||||
|
id: 1,
|
||||||
|
vendor_id: 1,
|
||||||
|
status: 'Success',
|
||||||
|
trigger: 'Manual',
|
||||||
|
start_time: new Date().toUTCString(),
|
||||||
|
};
|
||||||
|
const mockedAppConfigService = {
|
||||||
|
getConfig() {
|
||||||
|
return {
|
||||||
|
with_notary: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockPreheatService = {
|
||||||
|
ListPolicies: () => {
|
||||||
|
return of([policy1, policy2]).pipe(delay(0));
|
||||||
|
},
|
||||||
|
ListProvidersUnderProject() {
|
||||||
|
return of(providers).pipe(delay(0));
|
||||||
|
},
|
||||||
|
ListExecutionsResponse() {
|
||||||
|
return of([execution]).pipe(delay(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
ClarityModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
FormsModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
|
HttpClientTestingModule
|
||||||
|
],
|
||||||
|
declarations: [PolicyComponent,
|
||||||
|
AddP2pPolicyComponent,
|
||||||
|
InlineAlertComponent,
|
||||||
|
ConfirmationDialogComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
P2pProviderService,
|
||||||
|
ErrorHandler,
|
||||||
|
{ provide: PreheatService, useValue: mockPreheatService },
|
||||||
|
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
|
||||||
|
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||||
|
{ provide: UserPermissionService, useValue: mockUserPermissionService },
|
||||||
|
{ provide: SessionService, useValue: mockedSessionService },
|
||||||
|
{ provide: AppConfigService, useValue: mockedAppConfigService },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
fixture = TestBed.createComponent(PolicyComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.autoDetectChanges(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', async () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should get policy list', async () => {
|
||||||
|
await fixture.whenStable();
|
||||||
|
const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row');
|
||||||
|
expect(rows.length).toEqual(2);
|
||||||
|
});
|
||||||
|
it('should open modal and is add model', async () => {
|
||||||
|
await fixture.whenStable();
|
||||||
|
const addButton: HTMLButtonElement = fixture.nativeElement.querySelector('#new-policy');
|
||||||
|
addButton.click();
|
||||||
|
await fixture.whenStable();
|
||||||
|
const modalBody: HTMLDivElement = fixture.nativeElement.querySelector('.modal-body');
|
||||||
|
expect(modalBody).toBeTruthy();
|
||||||
|
const title: HTMLElement = fixture.nativeElement.querySelector('.modal-title');
|
||||||
|
expect(title.innerText).toEqual('P2P_PROVIDER.ADD_POLICY');
|
||||||
|
});
|
||||||
|
it('should open modal and is edit model', async () => {
|
||||||
|
component.selectedRow = policy1;
|
||||||
|
await fixture.whenStable();
|
||||||
|
const action: HTMLSpanElement = fixture.nativeElement.querySelector('#action-policy');
|
||||||
|
action.click();
|
||||||
|
await fixture.whenStable();
|
||||||
|
const edit: HTMLSpanElement = fixture.nativeElement.querySelector('#edit-policy');
|
||||||
|
edit.click();
|
||||||
|
await fixture.whenStable();
|
||||||
|
const modalBody: HTMLDivElement = fixture.nativeElement.querySelector('.modal-body');
|
||||||
|
expect(modalBody).toBeTruthy();
|
||||||
|
const nameInput: HTMLInputElement = fixture.nativeElement.querySelector('#name');
|
||||||
|
expect(nameInput.value).toEqual('policy1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,485 @@
|
|||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// 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 { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service';
|
||||||
|
import { Project } from '../../project';
|
||||||
|
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from '../../../shared/shared.const';
|
||||||
|
import { ConfirmationMessage } from '../../../shared/confirmation-dialog/confirmation-message';
|
||||||
|
import { ConfirmationDialogComponent } from '../../../shared/confirmation-dialog/confirmation-dialog.component';
|
||||||
|
import { clone, CustomComparator } from '../../../../lib/utils/utils';
|
||||||
|
import { forkJoin, Observable, Subject, Subscription } from 'rxjs';
|
||||||
|
import {
|
||||||
|
ClrDatagridComparatorInterface,
|
||||||
|
UserPermissionService,
|
||||||
|
USERSTATICPERMISSION
|
||||||
|
} from '../../../../lib/services';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import {
|
||||||
|
FILTER_TYPE,
|
||||||
|
P2pProviderService,
|
||||||
|
PROJECT_SEVERITY_LEVEL_TO_TEXT_MAP,
|
||||||
|
TIME_OUT,
|
||||||
|
TRIGGER,
|
||||||
|
TRIGGER_I18N_MAP
|
||||||
|
} from '../p2p-provider.service';
|
||||||
|
import { PreheatPolicy } from '../../../../../ng-swagger-gen/models/preheat-policy';
|
||||||
|
import { PreheatService } from '../../../../../ng-swagger-gen/services/preheat.service';
|
||||||
|
import { AddP2pPolicyComponent } from '../add-p2p-policy/add-p2p-policy.component';
|
||||||
|
import { Execution } from '../../../../../ng-swagger-gen/models/execution';
|
||||||
|
import { Metrics } from '../../../../../ng-swagger-gen/models/metrics';
|
||||||
|
import { ProviderUnderProject } from '../../../../../ng-swagger-gen/models/provider-under-project';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './policy.component.html',
|
||||||
|
styleUrls: ['./policy.component.scss']
|
||||||
|
})
|
||||||
|
export class PolicyComponent implements OnInit, OnDestroy {
|
||||||
|
@ViewChild(AddP2pPolicyComponent, { static: false } )
|
||||||
|
addP2pPolicyComponent: AddP2pPolicyComponent;
|
||||||
|
@ViewChild("confirmationDialogComponent", { static: false })
|
||||||
|
confirmationDialogComponent: ConfirmationDialogComponent;
|
||||||
|
projectId: number;
|
||||||
|
projectName: string;
|
||||||
|
selectedRow: PreheatPolicy;
|
||||||
|
policyList: PreheatPolicy[] = [];
|
||||||
|
providers: ProviderUnderProject[] = [];
|
||||||
|
metadata: any;
|
||||||
|
loading: boolean = false;
|
||||||
|
hasCreatPermission: boolean = false;
|
||||||
|
hasUpdatePermission: boolean = false;
|
||||||
|
hasDeletePermission: boolean = false;
|
||||||
|
addBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
executing: boolean = false;
|
||||||
|
isOpenFilterTag: boolean = false;
|
||||||
|
selectedExecutionRow: Execution;
|
||||||
|
jobsLoading: boolean = false;
|
||||||
|
stopLoading: boolean = false;
|
||||||
|
creationTimeComparator: ClrDatagridComparatorInterface<Execution> = new CustomComparator<Execution>("creation_time", "date");
|
||||||
|
executionList: Execution[] = [];
|
||||||
|
currentExecutionPage: number = 1;
|
||||||
|
pageSize: number = 10;
|
||||||
|
totalExecutionCount: number = 0;
|
||||||
|
filterKey: string = 'id';
|
||||||
|
searchString: string;
|
||||||
|
private _searchSubject: Subject<string> = new Subject<string>();
|
||||||
|
private _searchSubscription: Subscription;
|
||||||
|
project: Project;
|
||||||
|
severity_map: any = PROJECT_SEVERITY_LEVEL_TO_TEXT_MAP;
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private translate: TranslateService,
|
||||||
|
private p2pProviderService: P2pProviderService,
|
||||||
|
private messageHandlerService: MessageHandlerService,
|
||||||
|
private userPermissionService: UserPermissionService,
|
||||||
|
private preheatService: PreheatService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.subscribeSearch();
|
||||||
|
this.projectId = +this.route.snapshot.parent.parent.params['id'];
|
||||||
|
const resolverData = this.route.snapshot.parent.parent.data;
|
||||||
|
if (resolverData) {
|
||||||
|
const project = <Project>(resolverData["projectResolver"]);
|
||||||
|
this.projectName = project.name;
|
||||||
|
}
|
||||||
|
this.getPermissions();
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this._searchSubscription) {
|
||||||
|
this._searchSubscription.unsubscribe();
|
||||||
|
this._searchSubscription = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getPermissions() {
|
||||||
|
const permissionsList: Observable<boolean>[] = [];
|
||||||
|
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||||
|
USERSTATICPERMISSION.P2P_PROVIDER.KEY, USERSTATICPERMISSION.P2P_PROVIDER.VALUE.CREATE));
|
||||||
|
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||||
|
USERSTATICPERMISSION.P2P_PROVIDER.KEY, USERSTATICPERMISSION.P2P_PROVIDER.VALUE.UPDATE));
|
||||||
|
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||||
|
USERSTATICPERMISSION.P2P_PROVIDER.KEY, USERSTATICPERMISSION.P2P_PROVIDER.VALUE.DELETE));
|
||||||
|
this.addBtnState = ClrLoadingState.LOADING;
|
||||||
|
forkJoin(...permissionsList).subscribe(Rules => {
|
||||||
|
[this.hasCreatPermission, this.hasUpdatePermission, this.hasDeletePermission] = Rules;
|
||||||
|
this.addBtnState = ClrLoadingState.SUCCESS;
|
||||||
|
if (this.hasCreatPermission) {
|
||||||
|
this.getProviders();
|
||||||
|
}
|
||||||
|
}, error => {
|
||||||
|
this.messageHandlerService.error(error);
|
||||||
|
this.addBtnState = ClrLoadingState.ERROR;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getProviders() {
|
||||||
|
this.preheatService.ListProvidersUnderProject({projectName: this.projectName})
|
||||||
|
.subscribe(res => {
|
||||||
|
this.providers = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
refresh() {
|
||||||
|
this.selectedRow = null;
|
||||||
|
this.getPolicies();
|
||||||
|
}
|
||||||
|
getPolicies() {
|
||||||
|
this.loading = true;
|
||||||
|
this.preheatService.ListPolicies({projectName: this.projectName})
|
||||||
|
.pipe(finalize(() => (this.loading = false)))
|
||||||
|
.subscribe(
|
||||||
|
response => {
|
||||||
|
this.policyList = response;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.messageHandlerService.handleError(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
switchStatus() {
|
||||||
|
let content = '';
|
||||||
|
this.translate.get(
|
||||||
|
!this.selectedRow.enabled
|
||||||
|
? 'P2P_PROVIDER.ENABLED_POLICY_SUMMARY'
|
||||||
|
: 'P2P_PROVIDER.DISABLED_POLICY_SUMMARY'
|
||||||
|
, {name: this.selectedRow.name}).subscribe((res) => {
|
||||||
|
content = res;
|
||||||
|
let message = new ConfirmationMessage(
|
||||||
|
!this.selectedRow.enabled ? 'P2P_PROVIDER.ENABLED_POLICY_TITLE' : 'P2P_PROVIDER.DISABLED_POLICY_TITLE',
|
||||||
|
content,
|
||||||
|
'',
|
||||||
|
{},
|
||||||
|
ConfirmationTargets.P2P_PROVIDER,
|
||||||
|
!this.selectedRow.enabled ? ConfirmationButtons.ENABLE_CANCEL : ConfirmationButtons.DISABLE_CANCEL
|
||||||
|
);
|
||||||
|
this.confirmationDialogComponent.open(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
confirmSwitch(message) {
|
||||||
|
if (message && message.source === ConfirmationTargets.P2P_PROVIDER_STOP &&
|
||||||
|
message.state === ConfirmationState.CONFIRMED) {
|
||||||
|
this.stopLoading = true;
|
||||||
|
this.preheatService.StopExecution({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.selectedRow.name,
|
||||||
|
executionId: this.selectedExecutionRow.id,
|
||||||
|
execution: this.selectedExecutionRow
|
||||||
|
}).pipe(finalize(() => this.executing = false))
|
||||||
|
.subscribe(response => {
|
||||||
|
this.messageHandlerService.showSuccess('P2P_PROVIDER.STOP_SUCCESSFULLY');
|
||||||
|
}, error => {
|
||||||
|
this.messageHandlerService.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (message && message.source === ConfirmationTargets.P2P_PROVIDER_EXECUTE &&
|
||||||
|
message.state === ConfirmationState.CONFIRMED) {
|
||||||
|
this.executing = true;
|
||||||
|
this.preheatService.ManualPreheat({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.selectedRow.name,
|
||||||
|
policy: this.selectedRow
|
||||||
|
}).pipe(finalize(() => this.executing = false))
|
||||||
|
.subscribe(response => {
|
||||||
|
this.messageHandlerService.showSuccess('P2P_PROVIDER.EXECUTE_SUCCESSFULLY');
|
||||||
|
if (this.selectedRow) {
|
||||||
|
this.refreshJobs();
|
||||||
|
}
|
||||||
|
}, error => {
|
||||||
|
this.messageHandlerService.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (message &&
|
||||||
|
message.source === ConfirmationTargets.P2P_PROVIDER &&
|
||||||
|
message.state === ConfirmationState.CONFIRMED) {
|
||||||
|
if (JSON.stringify(message.data) === '{}') {
|
||||||
|
this.preheatService.UpdatePolicy({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.selectedRow.name,
|
||||||
|
policy: Object.assign({}, this.selectedRow, { enabled: !this.selectedRow.enabled })
|
||||||
|
}).subscribe(
|
||||||
|
response => {
|
||||||
|
this.messageHandlerService.showSuccess('P2P_PROVIDER.UPDATED_SUCCESSFULLY');
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.messageHandlerService.handleError(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message &&
|
||||||
|
message.source === ConfirmationTargets.P2P_PROVIDER_DELETE &&
|
||||||
|
message.state === ConfirmationState.CONFIRMED) {
|
||||||
|
const observableLists: Observable<any>[] = [];
|
||||||
|
observableLists.push(this.preheatService.DeletePolicy({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.selectedRow.name
|
||||||
|
}));
|
||||||
|
forkJoin(...observableLists).subscribe(
|
||||||
|
response => {
|
||||||
|
this.messageHandlerService.showSuccess('P2P_PROVIDER.DELETE_SUCCESSFULLY');
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.messageHandlerService.handleError(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPolicy() {
|
||||||
|
this.addP2pPolicyComponent.isOpen = true;
|
||||||
|
this.addP2pPolicyComponent.isEdit = false;
|
||||||
|
this.addP2pPolicyComponent.resetForAdd();
|
||||||
|
}
|
||||||
|
editPolicy() {
|
||||||
|
if (this.selectedRow) {
|
||||||
|
this.addP2pPolicyComponent.isOpen = true;
|
||||||
|
this.addP2pPolicyComponent.isEdit = true;
|
||||||
|
this.addP2pPolicyComponent.policy = clone(this.selectedRow);
|
||||||
|
const filter: any[] = JSON.parse(this.selectedRow.filters);
|
||||||
|
if (filter && filter.length) {
|
||||||
|
filter.forEach(item => {
|
||||||
|
if (item.type === FILTER_TYPE.REPOS && item.value) {
|
||||||
|
this.addP2pPolicyComponent.repos = item.value.replace(/[{}]/g, "");
|
||||||
|
}
|
||||||
|
if (item.type === FILTER_TYPE.TAG && item.value) {
|
||||||
|
this.addP2pPolicyComponent.tags = item.value.replace(/[{}]/g, "");
|
||||||
|
}
|
||||||
|
if (item.type === FILTER_TYPE.SIGNATURE) {
|
||||||
|
this.addP2pPolicyComponent.onlySignedImages = item.value;
|
||||||
|
}
|
||||||
|
if (item.type === FILTER_TYPE.LABEL && item.value) {
|
||||||
|
this.addP2pPolicyComponent.labels = item.value.replace(/[{}]/g, "");
|
||||||
|
}
|
||||||
|
if (item.type === FILTER_TYPE.VULNERABILITY) {
|
||||||
|
this.addP2pPolicyComponent.severity = item.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const trigger: any = JSON.parse(this.selectedRow.trigger);
|
||||||
|
if (trigger) {
|
||||||
|
this.addP2pPolicyComponent.triggerType = trigger.type;
|
||||||
|
this.addP2pPolicyComponent.cron = trigger.trigger_setting.cron;
|
||||||
|
}
|
||||||
|
this.addP2pPolicyComponent.currentForm.reset({
|
||||||
|
provider: this.addP2pPolicyComponent.policy.provider_id,
|
||||||
|
name: this.addP2pPolicyComponent.policy.name,
|
||||||
|
description: this.addP2pPolicyComponent.policy.description,
|
||||||
|
repo: this.addP2pPolicyComponent.repos,
|
||||||
|
tag: this.addP2pPolicyComponent.tags,
|
||||||
|
onlySignedImages: this.addP2pPolicyComponent.onlySignedImages,
|
||||||
|
severity: this.addP2pPolicyComponent.severity,
|
||||||
|
label: this.addP2pPolicyComponent.labels,
|
||||||
|
triggerType: this.addP2pPolicyComponent.triggerType
|
||||||
|
});
|
||||||
|
this.addP2pPolicyComponent.originPolicyForEdit = clone(this.selectedRow);
|
||||||
|
this.addP2pPolicyComponent.originReposForEdit = this.addP2pPolicyComponent.repos;
|
||||||
|
this.addP2pPolicyComponent.originTagsForEdit = this.addP2pPolicyComponent.tags;
|
||||||
|
this.addP2pPolicyComponent.originOnlySignedImagesForEdit = this.addP2pPolicyComponent.onlySignedImages;
|
||||||
|
this.addP2pPolicyComponent.originSeverityForEdit = this.addP2pPolicyComponent.severity;
|
||||||
|
this.addP2pPolicyComponent.originLabelsForEdit = this.addP2pPolicyComponent.labels;
|
||||||
|
this.addP2pPolicyComponent.originTriggerTypeForEdit = this.addP2pPolicyComponent.triggerType;
|
||||||
|
this.addP2pPolicyComponent.originCronForEdit = this.addP2pPolicyComponent.cron;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deletePolicy() {
|
||||||
|
const names: string[] = [];
|
||||||
|
names.push(this.selectedRow.name);
|
||||||
|
let content = '';
|
||||||
|
this.translate.get(
|
||||||
|
'P2P_PROVIDER.DELETE_POLICY_SUMMARY'
|
||||||
|
, {names: names.join(',')}).subscribe((res) => content = res);
|
||||||
|
const msg: ConfirmationMessage = new ConfirmationMessage(
|
||||||
|
"SCANNER.CONFIRM_DELETION",
|
||||||
|
content,
|
||||||
|
names.join(','),
|
||||||
|
this.selectedRow,
|
||||||
|
ConfirmationTargets.P2P_PROVIDER_DELETE,
|
||||||
|
ConfirmationButtons.DELETE_CANCEL
|
||||||
|
);
|
||||||
|
this.confirmationDialogComponent.open(msg);
|
||||||
|
}
|
||||||
|
executePolicy() {
|
||||||
|
if (this.selectedRow && this.selectedRow.enabled) {
|
||||||
|
const message = new ConfirmationMessage(
|
||||||
|
"P2P_PROVIDER.EXECUTE_TITLE",
|
||||||
|
"P2P_PROVIDER.EXECUTE_SUMMARY",
|
||||||
|
this.selectedRow.name,
|
||||||
|
this.selectedRow,
|
||||||
|
ConfirmationTargets.P2P_PROVIDER_EXECUTE,
|
||||||
|
ConfirmationButtons.CONFIRM_CANCEL
|
||||||
|
);
|
||||||
|
this.confirmationDialogComponent.open(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
success(isAdd: boolean) {
|
||||||
|
let message: string;
|
||||||
|
if (isAdd) {
|
||||||
|
message = 'P2P_PROVIDER.ADDED_SUCCESS';
|
||||||
|
} else {
|
||||||
|
message = 'P2P_PROVIDER.UPDATED_SUCCESS';
|
||||||
|
}
|
||||||
|
this.messageHandlerService.showSuccess(message);
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
clrLoadJobs(chosenPolicy: PreheatPolicy, withLoading: boolean) {
|
||||||
|
if (this.selectedRow) {
|
||||||
|
if (withLoading) {
|
||||||
|
// if datagrid is under control of *ngIf, should add timeout in case of ng changes checking error
|
||||||
|
setTimeout(() => {
|
||||||
|
this.jobsLoading = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let params: string;
|
||||||
|
if (this.searchString) {
|
||||||
|
params = encodeURIComponent(`${this.filterKey}=~${this.searchString}`);
|
||||||
|
}
|
||||||
|
this.preheatService.ListExecutionsResponse({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: chosenPolicy ? chosenPolicy.name : this.selectedRow.name,
|
||||||
|
page: this.currentExecutionPage,
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
q: params
|
||||||
|
}).pipe(finalize(() => this.jobsLoading = false))
|
||||||
|
.subscribe(response => {
|
||||||
|
if (response.headers) {
|
||||||
|
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)) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.clrLoadJobs(null, false);
|
||||||
|
}, TIME_OUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, error => {
|
||||||
|
this.messageHandlerService.handleError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshJobs (chosenPolicy?: PreheatPolicy) {
|
||||||
|
this.executionList = [];
|
||||||
|
this.currentExecutionPage = 1;
|
||||||
|
this.totalExecutionCount = 0;
|
||||||
|
this.filterKey = 'id';
|
||||||
|
this.searchString = null;
|
||||||
|
this.clrLoadJobs(chosenPolicy, true);
|
||||||
|
}
|
||||||
|
openStopExecutionsDialog() {
|
||||||
|
if (this.selectedExecutionRow) {
|
||||||
|
const stopExecutionsMessage = new ConfirmationMessage(
|
||||||
|
"P2P_PROVIDER.STOP_TITLE",
|
||||||
|
"P2P_PROVIDER.STOP_SUMMARY",
|
||||||
|
this.selectedExecutionRow.id + '',
|
||||||
|
this.selectedExecutionRow,
|
||||||
|
ConfirmationTargets.P2P_PROVIDER_STOP,
|
||||||
|
ConfirmationButtons.CONFIRM_CANCEL
|
||||||
|
);
|
||||||
|
this.confirmationDialogComponent.open(stopExecutionsMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goToLink(executionId: number) {
|
||||||
|
const linkUrl = ["harbor",
|
||||||
|
"projects", `${this.projectId}`, "p2p-provider", `${this.selectedRow.name}`, "executions", `${executionId}`, "tasks"];
|
||||||
|
this.router.navigate(linkUrl);
|
||||||
|
}
|
||||||
|
getTriggerTypeI18n(trigger: string): string {
|
||||||
|
if (JSON.parse(trigger).type) {
|
||||||
|
return TRIGGER_I18N_MAP[JSON.parse(trigger).type];
|
||||||
|
}
|
||||||
|
return TRIGGER_I18N_MAP[TRIGGER.MANUAL];
|
||||||
|
}
|
||||||
|
isScheduled(trigger: string): boolean {
|
||||||
|
return JSON.parse(trigger).type === TRIGGER.SCHEDULED;
|
||||||
|
}
|
||||||
|
isEventBased(trigger: string): boolean {
|
||||||
|
return JSON.parse(trigger).type === TRIGGER.EVENT_BASED;
|
||||||
|
}
|
||||||
|
getScheduledCron(trigger: string): string {
|
||||||
|
return JSON.parse(trigger).trigger_setting.cron;
|
||||||
|
}
|
||||||
|
getDuration(e: Execution): string {
|
||||||
|
return this.p2pProviderService.getDuration(e.start_time, e.end_time);
|
||||||
|
}
|
||||||
|
getValue(filter: string, type: string): string {
|
||||||
|
const arr: any[] = JSON.parse(filter);
|
||||||
|
if (arr && arr.length) {
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i].type === type && arr[i].value) {
|
||||||
|
return (arr[i].value + "").replace(/[{}]/g, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
getSuccessRate(m: Metrics): number {
|
||||||
|
if (m && m.task_count && m.success_task_count) {
|
||||||
|
return m.success_task_count / m.task_count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
selectFilterKey($event: any): void {
|
||||||
|
this.filterKey = $event['target'].value;
|
||||||
|
}
|
||||||
|
openFilter(isOpen: boolean): void {
|
||||||
|
this.isOpenFilterTag = isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
doFilter(terms: string): void {
|
||||||
|
this.searchString = terms;
|
||||||
|
if (terms.trim()) {
|
||||||
|
this._searchSubject.next(terms.trim());
|
||||||
|
} else {
|
||||||
|
this.clrLoadJobs(null, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscribeSearch() {
|
||||||
|
if (!this._searchSubscription) {
|
||||||
|
this._searchSubscription = this._searchSubject.pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
|
switchMap(searchString => {
|
||||||
|
this.jobsLoading = true;
|
||||||
|
let params: string;
|
||||||
|
if (this.searchString) {
|
||||||
|
params = encodeURIComponent(`${this.filterKey}=~${searchString}`);
|
||||||
|
}
|
||||||
|
return this.preheatService.ListExecutionsResponse({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.selectedRow.name,
|
||||||
|
page: 1,
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
q: params
|
||||||
|
}).pipe(finalize(() => this.jobsLoading = false));
|
||||||
|
})).subscribe(response => {
|
||||||
|
if (response.headers) {
|
||||||
|
let xHeader: string = response.headers.get('x-total-count');
|
||||||
|
if (xHeader) {
|
||||||
|
this.totalExecutionCount = parseInt(xHeader, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.executionList = response.body;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canStop(): boolean {
|
||||||
|
return this.selectedExecutionRow && this.p2pProviderService.willChangStatus(this.selectedExecutionRow.status);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
<div class="replication-tasks mt-1">
|
||||||
|
<section class="overview-section">
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<a (click)="onBack()" class="onBack"><{{'P2P_PROVIDER.P2P_PROVIDER'|
|
||||||
|
translate}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="title-block">
|
||||||
|
<div>
|
||||||
|
<h2 class="custom-h2 h2-style">{{'P2P_PROVIDER.EXECUTIONS'|
|
||||||
|
translate}}</h2>
|
||||||
|
<span class="id-divider"></span>
|
||||||
|
<h2 class="custom-h2 h2-style">{{executionId}}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="margin-top-075">
|
||||||
|
<div class="status-progress" *ngIf="execution && isInProgress()">
|
||||||
|
<span class="spinner spinner-inline"></span>
|
||||||
|
<span>{{'REPLICATION.IN_PROGRESS'| translate}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-success" *ngIf="execution && isSuccess()">
|
||||||
|
<clr-icon size="18" shape="success-standard" class="color-green"></clr-icon>
|
||||||
|
<span>{{'REPLICATION.SUCCESS'| translate}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-failed" *ngIf="execution && isFailed()">
|
||||||
|
<clr-icon size="18" shape="error-standard" class="color-red"></clr-icon>
|
||||||
|
<span>{{'REPLICATION.FAILURE'| translate}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="margin-top-075">
|
||||||
|
<button class="btn btn-primary " (click)="stopJob()"
|
||||||
|
[disabled]="!hasUpdatePermission ||stopOnGoing || !canStop()">{{'REPLICATION.STOPJOB' | translate}}</button>
|
||||||
|
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="execution-block">
|
||||||
|
<div class="executions-detail">
|
||||||
|
<div>
|
||||||
|
<label>{{'REPLICATION.TRIGGER_MODE' | translate}} :</label>
|
||||||
|
<span>{{trigger()| translate}}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>{{'REPLICATION.CREATION_TIME' | translate}} :</label>
|
||||||
|
<span>{{startTime() | date: 'short'}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-block">
|
||||||
|
<section class="execution-detail-label">
|
||||||
|
<section class="detail-row">
|
||||||
|
<div class="num-success common-style"></div>
|
||||||
|
<label class="detail-span">{{'REPLICATION.SUCCESS'| translate}}</label>
|
||||||
|
<div class="execution-details">{{successNum()}}</div>
|
||||||
|
</section>
|
||||||
|
<section class="detail-row">
|
||||||
|
<div class="num-failed common-style"></div>
|
||||||
|
<label class="detail-span">{{'REPLICATION.FAILURE'| translate}}</label>
|
||||||
|
<div class="execution-details">{{failedNum()}}</div>
|
||||||
|
</section>
|
||||||
|
<section class="detail-row">
|
||||||
|
<div class="num-progress common-style"></div>
|
||||||
|
<label class="detail-span">{{'REPLICATION.IN_PROGRESS'| translate}}</label>
|
||||||
|
<div class="execution-details">{{progressNum()}}</div>
|
||||||
|
</section>
|
||||||
|
<section class="detail-row">
|
||||||
|
<div class="num-stopped common-style"></div>
|
||||||
|
<label class="detail-span">{{'REPLICATION.STOPPED'| translate}}</label>
|
||||||
|
<div class="execution-details">{{stoppedNum()}}</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<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-column>{{'P2P_PROVIDER.EXTRA_ATTRS' | translate}}</clr-dg-column>
|
||||||
|
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'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-cell>{{t.id}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{t.execution_id}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{t.status}}
|
||||||
|
<clr-tooltip>
|
||||||
|
<clr-icon *ngIf="t.status_message" clrTooltipTrigger shape="info-circle" size="20"></clr-icon>
|
||||||
|
<clr-tooltip-content [clrPosition]="'left'" clrSize="md" *clrIfOpen>
|
||||||
|
<span>{{t.status_message}}</span>
|
||||||
|
</clr-tooltip-content>
|
||||||
|
</clr-tooltip>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{toJsonString(t.extra_attrs)}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{t.start_time | date: 'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{getDuration(t)}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<a target="_blank" [href]="viewLog(t.id)" *ngIf="t.status !== 'Initialized'">
|
||||||
|
<clr-icon shape="list"></clr-icon>
|
||||||
|
</a>
|
||||||
|
</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>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,115 @@
|
|||||||
|
.replication-tasks {
|
||||||
|
.overview-section {
|
||||||
|
.title-wrapper {
|
||||||
|
.onBack{
|
||||||
|
color: #007cbb;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.title-block {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
>div:first-child {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
>div:nth-child(2) {
|
||||||
|
width: 140px;
|
||||||
|
span {
|
||||||
|
color: #007cbb;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.id-divider {
|
||||||
|
display: inline-block;
|
||||||
|
height: 25px;
|
||||||
|
width: 2px;
|
||||||
|
background-color: #cccccc;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
.h2-style {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.execution-block {
|
||||||
|
margin-top: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: row wrap;
|
||||||
|
.execution-detail-label {
|
||||||
|
margin-right: 10px;
|
||||||
|
text-align: left;
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
height: 27px;
|
||||||
|
.common-style {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.num-success {
|
||||||
|
background-color: #308700;
|
||||||
|
}
|
||||||
|
.num-failed {
|
||||||
|
background-color: #C92101;
|
||||||
|
}
|
||||||
|
.num-progress {
|
||||||
|
background-color: #1C5898;
|
||||||
|
}
|
||||||
|
.num-stopped {
|
||||||
|
background-color: #A1A1A1;
|
||||||
|
}
|
||||||
|
.detail-span {
|
||||||
|
flex:0 0 100px;
|
||||||
|
margin: 10px 0 0 10px;
|
||||||
|
}
|
||||||
|
.execution-details {
|
||||||
|
width: 200px;
|
||||||
|
margin: 8px 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.executions-detail {
|
||||||
|
width: 391px;
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
>div {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tasks-detail {
|
||||||
|
margin-top: 65px;
|
||||||
|
.action-select {
|
||||||
|
padding-right: 18px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.filter-tag {
|
||||||
|
float: left;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.refresh-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover {
|
||||||
|
color: #007CBB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clr-datagrid {
|
||||||
|
margin-top: 20px;
|
||||||
|
.resource-width {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.margin-top-075 {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ClarityModule } from '@clr/angular';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { P2pProviderService } from '../p2p-provider.service';
|
||||||
|
import { PreheatService } from '../../../../../ng-swagger-gen/services/preheat.service';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { SessionService } from '../../../shared/session.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { delay } from 'rxjs/operators';
|
||||||
|
import { Execution } from '../../../../../ng-swagger-gen/models/execution';
|
||||||
|
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';
|
||||||
|
describe('TaskListComponent', () => {
|
||||||
|
let component: TaskListComponent;
|
||||||
|
let fixture: ComponentFixture<TaskListComponent>;
|
||||||
|
const execution: Execution = {
|
||||||
|
id: 1,
|
||||||
|
vendor_id: 1,
|
||||||
|
status: 'Success',
|
||||||
|
trigger: 'Manual',
|
||||||
|
start_time: new Date().toUTCString(),
|
||||||
|
};
|
||||||
|
const task: Task = {
|
||||||
|
id: 1,
|
||||||
|
execution_id: 1,
|
||||||
|
status: 'Success',
|
||||||
|
status_message: 'no artifact to preheat',
|
||||||
|
start_time: new Date().toUTCString(),
|
||||||
|
};
|
||||||
|
const mockPreheatService = {
|
||||||
|
GetExecution() {
|
||||||
|
return of(execution).pipe(delay(0));
|
||||||
|
},
|
||||||
|
ListTasks() {
|
||||||
|
return of([task]).pipe(delay(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockActivatedRoute = {
|
||||||
|
snapshot: {
|
||||||
|
params: {
|
||||||
|
executionId: 1,
|
||||||
|
preheatPolicyName: 'policy1'
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
parent: {
|
||||||
|
params: { id: 1 },
|
||||||
|
data: {
|
||||||
|
projectResolver: {
|
||||||
|
name: 'library',
|
||||||
|
metadata: {
|
||||||
|
prevent_vul: 'true',
|
||||||
|
enable_content_trust: 'true',
|
||||||
|
severity: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockedSessionService = {
|
||||||
|
getCurrentUser() {
|
||||||
|
return {
|
||||||
|
has_admin_role: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockMessageHandlerService = {
|
||||||
|
handleError: () => { }
|
||||||
|
};
|
||||||
|
const mockUserPermissionService = {
|
||||||
|
getPermission() {
|
||||||
|
return of(true).pipe(delay(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA,
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
ClarityModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
FormsModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
|
HttpClientTestingModule
|
||||||
|
],
|
||||||
|
declarations: [TaskListComponent],
|
||||||
|
providers: [
|
||||||
|
P2pProviderService,
|
||||||
|
TranslateService,
|
||||||
|
{ provide: PreheatService, useValue: mockPreheatService },
|
||||||
|
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||||
|
{ provide: SessionService, useValue: mockedSessionService },
|
||||||
|
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
|
||||||
|
{ provide: UserPermissionService, useValue: mockUserPermissionService },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TaskListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should render task list', async () => {
|
||||||
|
fixture.autoDetectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row');
|
||||||
|
expect(rows.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,223 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { finalize } from 'rxjs/operators';
|
||||||
|
import { CustomComparator, DEFAULT_PAGE_SIZE, isEmptyObject } 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 { 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 { ClrLoadingState } from '@clr/angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'task-list',
|
||||||
|
templateUrl: './task-list.component.html',
|
||||||
|
styleUrls: ['./task-list.component.scss']
|
||||||
|
})
|
||||||
|
export class TaskListComponent implements OnInit {
|
||||||
|
projectId: number;
|
||||||
|
projectName: string;
|
||||||
|
isOpenFilterTag: boolean;
|
||||||
|
inProgress: boolean = false;
|
||||||
|
currentPage: number = 1;
|
||||||
|
pageSize: number = DEFAULT_PAGE_SIZE;
|
||||||
|
totalCount: number;
|
||||||
|
loading = true;
|
||||||
|
tasks: Task[];
|
||||||
|
stopOnGoing: boolean;
|
||||||
|
executionId: string;
|
||||||
|
preheatPolicyName: string;
|
||||||
|
startTimeComparator: ClrDatagridComparatorInterface<Task> = new CustomComparator<Task>("start_time", "date");
|
||||||
|
execution: Execution;
|
||||||
|
hasUpdatePermission: boolean = false;
|
||||||
|
btnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
constructor(
|
||||||
|
private translate: TranslateService,
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private messageHandlerService: MessageHandlerService,
|
||||||
|
private preheatService: PreheatService,
|
||||||
|
private p2pProviderService: P2pProviderService,
|
||||||
|
private userPermissionService: UserPermissionService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.projectId = +this.route.snapshot.parent.parent.params['id'];
|
||||||
|
const resolverData = this.route.snapshot.parent.parent.data;
|
||||||
|
if (resolverData) {
|
||||||
|
let project = <Project>(resolverData["projectResolver"]);
|
||||||
|
this.projectName = project.name;
|
||||||
|
}
|
||||||
|
this.executionId = this.route.snapshot.params['executionId'];
|
||||||
|
this.preheatPolicyName = this.route.snapshot.params['preheatPolicyName'];
|
||||||
|
if (this.executionId && this.preheatPolicyName && this.projectName) {
|
||||||
|
this.getExecutionDetail(true);
|
||||||
|
}
|
||||||
|
this.getPermissions();
|
||||||
|
}
|
||||||
|
getPermissions() {
|
||||||
|
const permissionsList: Observable<boolean>[] = [];
|
||||||
|
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||||
|
USERSTATICPERMISSION.P2P_PROVIDER.KEY, USERSTATICPERMISSION.P2P_PROVIDER.VALUE.UPDATE));
|
||||||
|
this.btnState = ClrLoadingState.LOADING;
|
||||||
|
forkJoin(...permissionsList).subscribe(Rules => {
|
||||||
|
[this.hasUpdatePermission, ] = Rules;
|
||||||
|
this.btnState = ClrLoadingState.SUCCESS;
|
||||||
|
}, error => {
|
||||||
|
this.messageHandlerService.error(error);
|
||||||
|
this.btnState = ClrLoadingState.ERROR;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getExecutionDetail(withLoading: boolean): void {
|
||||||
|
if (withLoading) {
|
||||||
|
this.inProgress = true;
|
||||||
|
}
|
||||||
|
if (this.executionId) {
|
||||||
|
this.preheatService.GetExecution({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.preheatPolicyName,
|
||||||
|
executionId: +this.executionId
|
||||||
|
}).pipe(finalize(() => (this.inProgress = false)))
|
||||||
|
.subscribe(res => {
|
||||||
|
this.execution = res;
|
||||||
|
if (!this.execution || this.p2pProviderService.willChangStatus(this.execution.status)) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getExecutionDetail(false);
|
||||||
|
}, TIME_OUT);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.messageHandlerService.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trigger(): string {
|
||||||
|
return this.execution && this.execution.trigger
|
||||||
|
? this.execution.trigger
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime(): string {
|
||||||
|
return this.execution && this.execution.start_time
|
||||||
|
? this.execution.start_time
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
successNum(): number {
|
||||||
|
if (this.execution && this.execution.metrics) {
|
||||||
|
return this.execution.metrics.success_task_count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
failedNum(): number {
|
||||||
|
if (this.execution && this.execution.metrics) {
|
||||||
|
return this.execution.metrics.error_task_count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressNum(): number {
|
||||||
|
if (this.execution && this.execution.metrics) {
|
||||||
|
return (this.execution.metrics.pending_task_count ? this.execution.metrics.pending_task_count : 0)
|
||||||
|
+ (this.execution.metrics.running_task_count ? this.execution.metrics.running_task_count : 0)
|
||||||
|
+ (this.execution.metrics.scheduled_task_count ? this.execution.metrics.scheduled_task_count : 0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stoppedNum(): number {
|
||||||
|
if (this.execution && this.execution.metrics) {
|
||||||
|
return this.execution.metrics.stopped_task_count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopJob() {
|
||||||
|
this.stopOnGoing = true;
|
||||||
|
this.preheatService.StopExecution({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.preheatPolicyName,
|
||||||
|
executionId: +this.executionId,
|
||||||
|
execution: this.execution
|
||||||
|
})
|
||||||
|
.subscribe(response => {
|
||||||
|
this.stopOnGoing = false;
|
||||||
|
this.getExecutionDetail(true);
|
||||||
|
this.translate.get("REPLICATION.STOP_SUCCESS", { param: this.executionId }).subscribe((res: string) => {
|
||||||
|
this.messageHandlerService.showSuccess(res);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.messageHandlerService.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
viewLog(taskId: number | string): string {
|
||||||
|
return this.preheatService.rootUrl
|
||||||
|
+ `/projects/${this.projectName}/preheat/policies/${this.preheatPolicyName}/executions/${this.executionId}/tasks/${taskId}/logs`;
|
||||||
|
}
|
||||||
|
clrLoadTasks(withLoading): void {
|
||||||
|
if (withLoading) {
|
||||||
|
this.loading = true;
|
||||||
|
}
|
||||||
|
this.preheatService.ListTasks({
|
||||||
|
projectName: this.projectName,
|
||||||
|
preheatPolicyName: this.preheatPolicyName,
|
||||||
|
executionId: +this.executionId
|
||||||
|
})
|
||||||
|
.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)) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.clrLoadTasks(false);
|
||||||
|
}, TIME_OUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.messageHandlerService.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onBack(): void {
|
||||||
|
this.router.navigate(["harbor", "projects", `${this.projectId}`, "p2p-provider", "policies"]);
|
||||||
|
}
|
||||||
|
// refresh icon
|
||||||
|
refreshTasks(): void {
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.totalCount = 0;
|
||||||
|
this.clrLoadTasks(true);
|
||||||
|
}
|
||||||
|
toJsonString(obj: any) {
|
||||||
|
if (!isEmptyObject(obj)) {
|
||||||
|
return JSON.stringify(obj);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
getDuration(t: Task): string {
|
||||||
|
return this.p2pProviderService.getDuration(t.start_time, t.end_time);
|
||||||
|
}
|
||||||
|
isInProgress(): boolean {
|
||||||
|
return this.execution && this.p2pProviderService.willChangStatus(this.execution.status);
|
||||||
|
}
|
||||||
|
isSuccess(): boolean {
|
||||||
|
return this.execution && this.execution.status === EXECUTION_STATUS.SUCCESS;
|
||||||
|
}
|
||||||
|
isFailed(): boolean {
|
||||||
|
return this.execution && (this.execution.status === EXECUTION_STATUS.ERROR || this.execution.status === EXECUTION_STATUS.STOPPED);
|
||||||
|
}
|
||||||
|
canStop(): boolean {
|
||||||
|
return this.execution && this.p2pProviderService.willChangStatus(this.execution.status);
|
||||||
|
}
|
||||||
|
}
|
@ -47,6 +47,7 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
hasTagRetentionPermission: boolean;
|
hasTagRetentionPermission: boolean;
|
||||||
hasWebhookListPermission: boolean;
|
hasWebhookListPermission: boolean;
|
||||||
hasScannerReadPermission: boolean;
|
hasScannerReadPermission: boolean;
|
||||||
|
hasP2pProviderReadPermission: boolean;
|
||||||
tabLinkNavList = [
|
tabLinkNavList = [
|
||||||
{
|
{
|
||||||
linkName: "summary",
|
linkName: "summary",
|
||||||
@ -84,6 +85,12 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
showTabName: "SCANNER.SCANNER",
|
showTabName: "SCANNER.SCANNER",
|
||||||
permissions: () => this.hasScannerReadPermission
|
permissions: () => this.hasScannerReadPermission
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
linkName: "p2p-provider",
|
||||||
|
tabLinkInOverflow: false,
|
||||||
|
showTabName: "P2P_PROVIDER.P2P_PROVIDER",
|
||||||
|
permissions: () => this.hasP2pProviderReadPermission
|
||||||
|
},
|
||||||
{
|
{
|
||||||
linkName: "tag-strategy",
|
linkName: "tag-strategy",
|
||||||
tabLinkInOverflow: false,
|
tabLinkInOverflow: false,
|
||||||
@ -193,12 +200,14 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
USERSTATICPERMISSION.WEBHOOK.KEY, USERSTATICPERMISSION.WEBHOOK.VALUE.LIST));
|
USERSTATICPERMISSION.WEBHOOK.KEY, USERSTATICPERMISSION.WEBHOOK.VALUE.LIST));
|
||||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||||
USERSTATICPERMISSION.SCANNER.KEY, USERSTATICPERMISSION.SCANNER.VALUE.READ));
|
USERSTATICPERMISSION.SCANNER.KEY, USERSTATICPERMISSION.SCANNER.VALUE.READ));
|
||||||
|
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||||
|
USERSTATICPERMISSION.P2P_PROVIDER.KEY, USERSTATICPERMISSION.P2P_PROVIDER.VALUE.READ));
|
||||||
|
|
||||||
forkJoin(...permissionsList).subscribe(Rules => {
|
forkJoin(...permissionsList).subscribe(Rules => {
|
||||||
[this.hasProjectReadPermission, this.hasLogListPermission, this.hasConfigurationListPermission, this.hasMemberListPermission
|
[this.hasProjectReadPermission, this.hasLogListPermission, this.hasConfigurationListPermission, this.hasMemberListPermission
|
||||||
, this.hasLabelListPermission, this.hasRepositoryListPermission, this.hasHelmChartsListPermission, this.hasRobotListPermission
|
, this.hasLabelListPermission, this.hasRepositoryListPermission, this.hasHelmChartsListPermission, this.hasRobotListPermission
|
||||||
, this.hasLabelCreatePermission, this.hasTagRetentionPermission, this.hasWebhookListPermission,
|
, this.hasLabelCreatePermission, this.hasTagRetentionPermission, this.hasWebhookListPermission,
|
||||||
this.hasScannerReadPermission] = Rules;
|
this.hasScannerReadPermission, this.hasP2pProviderReadPermission] = Rules;
|
||||||
}, error => this.errorHandler.error(error));
|
}, error => this.errorHandler.error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ import { AddMemberComponent } from './member/add-member/add-member.component';
|
|||||||
import { AddGroupComponent } from './member/add-group/add-group.component';
|
import { AddGroupComponent } from './member/add-group/add-group.component';
|
||||||
import { MemberService } from './member/member.service';
|
import { MemberService } from './member/member.service';
|
||||||
import { RobotService } from './robot-account/robot-account.service';
|
import { RobotService } from './robot-account/robot-account.service';
|
||||||
import { ProjectRoutingResolver } from '../services/routing-resolvers/project-routing-resolver.service';
|
|
||||||
import { TargetExistsValidatorDirective } from '../shared/target-exists-directive';
|
import { TargetExistsValidatorDirective } from '../shared/target-exists-directive';
|
||||||
import { HelmChartModule } from './helm-chart/helm-chart.module';
|
import { HelmChartModule } from './helm-chart/helm-chart.module';
|
||||||
import { RobotAccountComponent } from './robot-account/robot-account.component';
|
import { RobotAccountComponent } from './robot-account/robot-account.component';
|
||||||
@ -64,6 +63,11 @@ import { ArtifactDefaultService, ArtifactService } from "./repository/artifact/a
|
|||||||
import { GridViewComponent } from "./repository/gridview/grid-view.component";
|
import { GridViewComponent } from "./repository/gridview/grid-view.component";
|
||||||
import { LastTriggerComponent } from "./webhook/last-trigger/last-trigger.component";
|
import { LastTriggerComponent } from "./webhook/last-trigger/last-trigger.component";
|
||||||
import { AllPipesModule } from '../all-pipes/all-pipes.module';
|
import { AllPipesModule } from '../all-pipes/all-pipes.module';
|
||||||
|
import { PolicyComponent } from './p2p-provider/policy/policy.component';
|
||||||
|
import { P2pProviderService } from './p2p-provider/p2p-provider.service';
|
||||||
|
import { AddP2pPolicyComponent } from './p2p-provider/add-p2p-policy/add-p2p-policy.component';
|
||||||
|
import { TaskListComponent } from './p2p-provider/task-list/task-list.component';
|
||||||
|
import { P2pProviderComponent } from './p2p-provider/p2p-provider.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -112,7 +116,11 @@ import { AllPipesModule } from '../all-pipes/all-pipes.module';
|
|||||||
ValuesComponent,
|
ValuesComponent,
|
||||||
ArtifactVulnerabilitiesComponent,
|
ArtifactVulnerabilitiesComponent,
|
||||||
GridViewComponent,
|
GridViewComponent,
|
||||||
LastTriggerComponent
|
LastTriggerComponent,
|
||||||
|
PolicyComponent,
|
||||||
|
AddP2pPolicyComponent,
|
||||||
|
TaskListComponent,
|
||||||
|
P2pProviderComponent
|
||||||
],
|
],
|
||||||
exports: [ProjectComponent, ListProjectComponent],
|
exports: [ProjectComponent, ListProjectComponent],
|
||||||
providers: [
|
providers: [
|
||||||
@ -122,6 +130,7 @@ import { AllPipesModule } from '../all-pipes/all-pipes.module';
|
|||||||
ConfigScannerService,
|
ConfigScannerService,
|
||||||
ArtifactDefaultService,
|
ArtifactDefaultService,
|
||||||
{ provide: ArtifactService, useClass: ArtifactDefaultService },
|
{ provide: ArtifactService, useClass: ArtifactDefaultService },
|
||||||
|
P2pProviderService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ProjectModule {
|
export class ProjectModule {
|
||||||
|
@ -44,7 +44,11 @@ export const enum ConfirmationTargets {
|
|||||||
HELM_CHART_VERSION,
|
HELM_CHART_VERSION,
|
||||||
WEBHOOK,
|
WEBHOOK,
|
||||||
SCANNER,
|
SCANNER,
|
||||||
INSTANCE
|
INSTANCE,
|
||||||
|
P2P_PROVIDER,
|
||||||
|
P2P_PROVIDER_EXECUTE,
|
||||||
|
P2P_PROVIDER_STOP,
|
||||||
|
P2P_PROVIDER_DELETE
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ActionType {
|
export const enum ActionType {
|
||||||
@ -96,6 +100,7 @@ export enum Roles {
|
|||||||
OTHER = 0,
|
OTHER = 0,
|
||||||
}
|
}
|
||||||
export const DefaultHelmIcon = '/images/helm-gray.svg';
|
export const DefaultHelmIcon = '/images/helm-gray.svg';
|
||||||
|
|
||||||
export enum ResourceType {
|
export enum ResourceType {
|
||||||
REPOSITORY = 1,
|
REPOSITORY = 1,
|
||||||
CHART_VERSION = 2,
|
CHART_VERSION = 2,
|
||||||
|
@ -1500,5 +1500,69 @@
|
|||||||
"SET_DEFAULT_FAILED": "Setting as default failed",
|
"SET_DEFAULT_FAILED": "Setting as default failed",
|
||||||
"UPDATE_INSTANCE": "Update instance",
|
"UPDATE_INSTANCE": "Update instance",
|
||||||
"CREATE_INSTANCE": "Create instance"
|
"CREATE_INSTANCE": "Create instance"
|
||||||
|
},
|
||||||
|
"P2P_PROVIDER": {
|
||||||
|
"P2P_PROVIDER": "Preheat",
|
||||||
|
"POLICIES": "Policies",
|
||||||
|
"NEW_POLICY": "NEW POLICY",
|
||||||
|
"NAME": "Name",
|
||||||
|
"ENABLED": "Enabled",
|
||||||
|
"PROVIDER": "Provider",
|
||||||
|
"FILTERS": "Filters",
|
||||||
|
"TRIGGER": "Trigger",
|
||||||
|
"CREATED": "Creation Time",
|
||||||
|
"DESCRIPTION": "Description",
|
||||||
|
"NO_POLICY": "No policy",
|
||||||
|
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||||
|
"DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?",
|
||||||
|
"ENABLED_POLICY_TITLE": "Enable Policy",
|
||||||
|
"DISABLED_POLICY_TITLE": "Disable Policy",
|
||||||
|
"DELETE_POLICY_SUMMARY": "Do you want to delete policy {{names}}?",
|
||||||
|
"EDIT_POLICY": "Edit P2P Provider Policy",
|
||||||
|
"ADD_POLICY": "Create P2P Provider policy",
|
||||||
|
"NAME_REQUIRED": "Name is required",
|
||||||
|
"PROVIDER_REQUIRED": "Provider is required",
|
||||||
|
"ADDED_SUCCESS": "Added policy successfully",
|
||||||
|
"UPDATED_SUCCESS": "Updated policy successfully",
|
||||||
|
"EXECUTE": "Execute",
|
||||||
|
"EXECUTE_SUCCESSFULLY": "Executed successfully",
|
||||||
|
"EXECUTE_TITLE": "Confirm Policy Execution",
|
||||||
|
"EXECUTE_SUMMARY": "Do you want to execute the policy {{param}}?",
|
||||||
|
"STOP_TITLE": "Confirm Execution Stopping",
|
||||||
|
"STOP_SUMMARY": "Do you want to stop executing the policy {{param}}?",
|
||||||
|
"STOP_SUCCESSFULLY": "Stopped execution successfully",
|
||||||
|
"STATUS_MSG": "Status Message",
|
||||||
|
"JOB_PLACEHOLDER": "We couldn't find any execution",
|
||||||
|
"PROVIDER_TYPE": "Vendor",
|
||||||
|
"ID": "Execution ID",
|
||||||
|
"NO_PROVIDER": "Please add a provider first",
|
||||||
|
"EXTRA_ATTRS": "Extra Attrs",
|
||||||
|
"TASKS": "Tasks",
|
||||||
|
"TASKS_PLACEHOLDER": "We couldn't find any task",
|
||||||
|
"SEVERITY_WARNING": "Vulnerability settings here conflicts with the relevant project configuration that will override the settings here",
|
||||||
|
"REPOS": "Repositories",
|
||||||
|
"TAGS": "Tags",
|
||||||
|
"CRITERIA": "Criteria",
|
||||||
|
"ONLY_SIGNED": "Only signed images",
|
||||||
|
"PREHEAT_ON_PUSH": "Preheat on push",
|
||||||
|
"START_TEXT": "No vulnerability severity of",
|
||||||
|
"EDN_TEXT": "and above",
|
||||||
|
"LABELS": "Labels",
|
||||||
|
"SCHEDULE": "Schedule",
|
||||||
|
"TEST_FAILED": "Test failed",
|
||||||
|
"MANUAL": "Manual",
|
||||||
|
"SCHEDULED": "Scheduled",
|
||||||
|
"EVENT_BASED": "Event based",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE1": "The policy will be evaluated whenever the following events are occurred:",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE2": "Artifact is pushed",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE3": "Artifact is labeled",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE4": "Artifact is scanned",
|
||||||
|
"REPO_REQUIRED": "Repository is required",
|
||||||
|
"TAG_REQUIRED": "Tag is required",
|
||||||
|
"DELETE_SUCCESSFULLY": "Deleted policy successfully",
|
||||||
|
"UPDATED_SUCCESSFULLY": "Updated policy successfully",
|
||||||
|
"EXECUTIONS": "Executions",
|
||||||
|
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,or **",
|
||||||
|
"CONTENT_WARNING": "Content trust settings here conflicts with the relevant project configuration that will override the settings here"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1494,5 +1494,69 @@
|
|||||||
"SET_DEFAULT_FAILED": "Setting as default failed",
|
"SET_DEFAULT_FAILED": "Setting as default failed",
|
||||||
"UPDATE_INSTANCE": "Update instance",
|
"UPDATE_INSTANCE": "Update instance",
|
||||||
"CREATE_INSTANCE": "Create instance"
|
"CREATE_INSTANCE": "Create instance"
|
||||||
|
},
|
||||||
|
"P2P_PROVIDER": {
|
||||||
|
"P2P_PROVIDER": "Preheat",
|
||||||
|
"POLICIES": "Policies",
|
||||||
|
"NEW_POLICY": "NEW POLICY",
|
||||||
|
"NAME": "Name",
|
||||||
|
"ENABLED": "Enabled",
|
||||||
|
"PROVIDER": "Provider",
|
||||||
|
"FILTERS": "Filters",
|
||||||
|
"TRIGGER": "Trigger",
|
||||||
|
"CREATED": "Creation Time",
|
||||||
|
"DESCRIPTION": "Description",
|
||||||
|
"NO_POLICY": "No policy",
|
||||||
|
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||||
|
"DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?",
|
||||||
|
"ENABLED_POLICY_TITLE": "Enable Policy",
|
||||||
|
"DISABLED_POLICY_TITLE": "Disable Policy",
|
||||||
|
"DELETE_POLICY_SUMMARY": "Do you want to delete policy {{names}}?",
|
||||||
|
"EDIT_POLICY": "Edit P2P Provider Policy",
|
||||||
|
"ADD_POLICY": "Create P2P Provider policy",
|
||||||
|
"NAME_REQUIRED": "Name is required",
|
||||||
|
"PROVIDER_REQUIRED": "Provider is required",
|
||||||
|
"ADDED_SUCCESS": "Added policy successfully",
|
||||||
|
"UPDATED_SUCCESS": "Updated policy successfully",
|
||||||
|
"EXECUTE": "Execute",
|
||||||
|
"EXECUTE_SUCCESSFULLY": "Executed successfully",
|
||||||
|
"EXECUTE_TITLE": "Confirm Policy Execution",
|
||||||
|
"EXECUTE_SUMMARY": "Do you want to execute the policy {{param}}?",
|
||||||
|
"STOP_TITLE": "Confirm Execution Stopping",
|
||||||
|
"STOP_SUMMARY": "Do you want to stop executing the policy {{param}}?",
|
||||||
|
"STOP_SUCCESSFULLY": "Stopped execution successfully",
|
||||||
|
"STATUS_MSG": "Status Message",
|
||||||
|
"JOB_PLACEHOLDER": "We couldn't find any execution",
|
||||||
|
"PROVIDER_TYPE": "Vendor",
|
||||||
|
"ID": "Execution ID",
|
||||||
|
"NO_PROVIDER": "Please add a provider first",
|
||||||
|
"EXTRA_ATTRS": "Extra Attrs",
|
||||||
|
"TASKS": "Tasks",
|
||||||
|
"TASKS_PLACEHOLDER": "We couldn't find any task",
|
||||||
|
"SEVERITY_WARNING": "Vulnerability settings here conflicts with the relevant project configuration that will override the settings here",
|
||||||
|
"REPOS": "Repositories",
|
||||||
|
"TAGS": "Tags",
|
||||||
|
"CRITERIA": "Criteria",
|
||||||
|
"ONLY_SIGNED": "Only signed images",
|
||||||
|
"PREHEAT_ON_PUSH": "Preheat on push",
|
||||||
|
"START_TEXT": "No vulnerability severity of",
|
||||||
|
"EDN_TEXT": "and above",
|
||||||
|
"LABELS": "Labels",
|
||||||
|
"SCHEDULE": "Schedule",
|
||||||
|
"TEST_FAILED": "Test failed",
|
||||||
|
"MANUAL": "Manual",
|
||||||
|
"SCHEDULED": "Scheduled",
|
||||||
|
"EVENT_BASED": "Event based",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE1": "The policy will be evaluated whenever the following events are occurred:",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE2": "Artifact is pushed",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE3": "Artifact is labeled",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE4": "Artifact is scanned",
|
||||||
|
"REPO_REQUIRED": "Repository is required",
|
||||||
|
"TAG_REQUIRED": "Tag is required",
|
||||||
|
"DELETE_SUCCESSFULLY": "Deleted policy successfully",
|
||||||
|
"UPDATED_SUCCESSFULLY": "Updated policy successfully",
|
||||||
|
"EXECUTIONS": "Executions",
|
||||||
|
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,or **",
|
||||||
|
"CONTENT_WARNING": "Content trust settings here conflicts with the relevant project configuration that will override the settings here"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1464,5 +1464,69 @@
|
|||||||
"SET_DEFAULT_FAILED": "Setting as default failed",
|
"SET_DEFAULT_FAILED": "Setting as default failed",
|
||||||
"UPDATE_INSTANCE": "Update instance",
|
"UPDATE_INSTANCE": "Update instance",
|
||||||
"CREATE_INSTANCE": "Create instance"
|
"CREATE_INSTANCE": "Create instance"
|
||||||
|
},
|
||||||
|
"P2P_PROVIDER": {
|
||||||
|
"P2P_PROVIDER": "Preheat",
|
||||||
|
"POLICIES": "Policies",
|
||||||
|
"NEW_POLICY": "NEW POLICY",
|
||||||
|
"NAME": "Name",
|
||||||
|
"ENABLED": "Enabled",
|
||||||
|
"PROVIDER": "Provider",
|
||||||
|
"FILTERS": "Filters",
|
||||||
|
"TRIGGER": "Trigger",
|
||||||
|
"CREATED": "Creation Time",
|
||||||
|
"DESCRIPTION": "Description",
|
||||||
|
"NO_POLICY": "No policy",
|
||||||
|
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||||
|
"DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?",
|
||||||
|
"ENABLED_POLICY_TITLE": "Enable Policy",
|
||||||
|
"DISABLED_POLICY_TITLE": "Disable Policy",
|
||||||
|
"DELETE_POLICY_SUMMARY": "Do you want to delete policy {{names}}?",
|
||||||
|
"EDIT_POLICY": "Edit P2P Provider Policy",
|
||||||
|
"ADD_POLICY": "Create P2P Provider policy",
|
||||||
|
"NAME_REQUIRED": "Name is required",
|
||||||
|
"PROVIDER_REQUIRED": "Provider is required",
|
||||||
|
"ADDED_SUCCESS": "Added policy successfully",
|
||||||
|
"UPDATED_SUCCESS": "Updated policy successfully",
|
||||||
|
"EXECUTE": "Execute",
|
||||||
|
"EXECUTE_SUCCESSFULLY": "Executed successfully",
|
||||||
|
"EXECUTE_TITLE": "Confirm Policy Execution",
|
||||||
|
"EXECUTE_SUMMARY": "Do you want to execute the policy {{param}}?",
|
||||||
|
"STOP_TITLE": "Confirm Execution Stopping",
|
||||||
|
"STOP_SUMMARY": "Do you want to stop executing the policy {{param}}?",
|
||||||
|
"STOP_SUCCESSFULLY": "Stopped execution successfully",
|
||||||
|
"STATUS_MSG": "Status Message",
|
||||||
|
"JOB_PLACEHOLDER": "We couldn't find any execution",
|
||||||
|
"PROVIDER_TYPE": "Vendor",
|
||||||
|
"ID": "Execution ID",
|
||||||
|
"NO_PROVIDER": "Please add a provider first",
|
||||||
|
"EXTRA_ATTRS": "Extra Attrs",
|
||||||
|
"TASKS": "Tasks",
|
||||||
|
"TASKS_PLACEHOLDER": "We couldn't find any task",
|
||||||
|
"SEVERITY_WARNING": "Vulnerability settings here conflicts with the relevant project configuration that will override the settings here",
|
||||||
|
"REPOS": "Repositories",
|
||||||
|
"TAGS": "Tags",
|
||||||
|
"CRITERIA": "Criteria",
|
||||||
|
"ONLY_SIGNED": "Only signed images",
|
||||||
|
"PREHEAT_ON_PUSH": "Preheat on push",
|
||||||
|
"START_TEXT": "No vulnerability severity of",
|
||||||
|
"EDN_TEXT": "and above",
|
||||||
|
"LABELS": "Labels",
|
||||||
|
"SCHEDULE": "Schedule",
|
||||||
|
"TEST_FAILED": "Test failed",
|
||||||
|
"MANUAL": "Manual",
|
||||||
|
"SCHEDULED": "Scheduled",
|
||||||
|
"EVENT_BASED": "Event based",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE1": "The policy will be evaluated whenever the following events are occurred:",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE2": "Artifact is pushed",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE3": "Artifact is labeled",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE4": "Artifact is scanned",
|
||||||
|
"REPO_REQUIRED": "Repository is required",
|
||||||
|
"TAG_REQUIRED": "Tag is required",
|
||||||
|
"DELETE_SUCCESSFULLY": "Deleted policy successfully",
|
||||||
|
"UPDATED_SUCCESSFULLY": "Updated policy successfully",
|
||||||
|
"EXECUTIONS": "Executions",
|
||||||
|
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,or **",
|
||||||
|
"CONTENT_WARNING": "Content trust settings here conflicts with the relevant project configuration that will override the settings here"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1492,6 +1492,70 @@
|
|||||||
"SET_DEFAULT_FAILED": "Setting as default failed",
|
"SET_DEFAULT_FAILED": "Setting as default failed",
|
||||||
"UPDATE_INSTANCE": "Update instance",
|
"UPDATE_INSTANCE": "Update instance",
|
||||||
"CREATE_INSTANCE": "Create instance"
|
"CREATE_INSTANCE": "Create instance"
|
||||||
|
},
|
||||||
|
"P2P_PROVIDER": {
|
||||||
|
"P2P_PROVIDER": "Preheat",
|
||||||
|
"POLICIES": "Policies",
|
||||||
|
"NEW_POLICY": "NEW POLICY",
|
||||||
|
"NAME": "Name",
|
||||||
|
"ENABLED": "Enabled",
|
||||||
|
"PROVIDER": "Provider",
|
||||||
|
"FILTERS": "Filters",
|
||||||
|
"TRIGGER": "Trigger",
|
||||||
|
"CREATED": "Creation Time",
|
||||||
|
"DESCRIPTION": "Description",
|
||||||
|
"NO_POLICY": "No policy",
|
||||||
|
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||||
|
"DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?",
|
||||||
|
"ENABLED_POLICY_TITLE": "Enable Policy",
|
||||||
|
"DISABLED_POLICY_TITLE": "Disable Policy",
|
||||||
|
"DELETE_POLICY_SUMMARY": "Do you want to delete policy {{names}}?",
|
||||||
|
"EDIT_POLICY": "Edit P2P Provider Policy",
|
||||||
|
"ADD_POLICY": "Create P2P Provider policy",
|
||||||
|
"NAME_REQUIRED": "Name is required",
|
||||||
|
"PROVIDER_REQUIRED": "Provider is required",
|
||||||
|
"ADDED_SUCCESS": "Added policy successfully",
|
||||||
|
"UPDATED_SUCCESS": "Updated policy successfully",
|
||||||
|
"EXECUTE": "Execute",
|
||||||
|
"EXECUTE_SUCCESSFULLY": "Executed successfully",
|
||||||
|
"EXECUTE_TITLE": "Confirm Policy Execution",
|
||||||
|
"EXECUTE_SUMMARY": "Do you want to execute the policy {{param}}?",
|
||||||
|
"STOP_TITLE": "Confirm Execution Stopping",
|
||||||
|
"STOP_SUMMARY": "Do you want to stop executing the policy {{param}}?",
|
||||||
|
"STOP_SUCCESSFULLY": "Stopped execution successfully",
|
||||||
|
"STATUS_MSG": "Status Message",
|
||||||
|
"JOB_PLACEHOLDER": "We couldn't find any execution",
|
||||||
|
"PROVIDER_TYPE": "Vendor",
|
||||||
|
"ID": "Execution ID",
|
||||||
|
"NO_PROVIDER": "Please add a provider first",
|
||||||
|
"EXTRA_ATTRS": "Extra Attrs",
|
||||||
|
"TASKS": "Tasks",
|
||||||
|
"TASKS_PLACEHOLDER": "We couldn't find any task",
|
||||||
|
"SEVERITY_WARNING": "Vulnerability settings here conflicts with the relevant project configuration that will override the settings here",
|
||||||
|
"REPOS": "Repositories",
|
||||||
|
"TAGS": "Tags",
|
||||||
|
"CRITERIA": "Criteria",
|
||||||
|
"ONLY_SIGNED": "Only signed images",
|
||||||
|
"PREHEAT_ON_PUSH": "Preheat on push",
|
||||||
|
"START_TEXT": "No vulnerability severity of",
|
||||||
|
"EDN_TEXT": "and above",
|
||||||
|
"LABELS": "Labels",
|
||||||
|
"SCHEDULE": "Schedule",
|
||||||
|
"TEST_FAILED": "Test failed",
|
||||||
|
"MANUAL": "Manual",
|
||||||
|
"SCHEDULED": "Scheduled",
|
||||||
|
"EVENT_BASED": "Event based",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE1": "The policy will be evaluated whenever the following events are occurred:",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE2": "Artifact is pushed",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE3": "Artifact is labeled",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE4": "Artifact is scanned",
|
||||||
|
"REPO_REQUIRED": "Repository is required",
|
||||||
|
"TAG_REQUIRED": "Tag is required",
|
||||||
|
"DELETE_SUCCESSFULLY": "Deleted policy successfully",
|
||||||
|
"UPDATED_SUCCESSFULLY": "Updated policy successfully",
|
||||||
|
"EXECUTIONS": "Executions",
|
||||||
|
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,or **",
|
||||||
|
"CONTENT_WARNING": "Content trust settings here conflicts with the relevant project configuration that will override the settings here"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1496,5 +1496,69 @@
|
|||||||
"SET_DEFAULT_FAILED": "Setting as default failed",
|
"SET_DEFAULT_FAILED": "Setting as default failed",
|
||||||
"UPDATE_INSTANCE": "Update instance",
|
"UPDATE_INSTANCE": "Update instance",
|
||||||
"CREATE_INSTANCE": "Create instance"
|
"CREATE_INSTANCE": "Create instance"
|
||||||
|
},
|
||||||
|
"P2P_PROVIDER": {
|
||||||
|
"P2P_PROVIDER": "Preheat",
|
||||||
|
"POLICIES": "Policies",
|
||||||
|
"NEW_POLICY": "NEW POLICY",
|
||||||
|
"NAME": "Name",
|
||||||
|
"ENABLED": "Enabled",
|
||||||
|
"PROVIDER": "Provider",
|
||||||
|
"FILTERS": "Filters",
|
||||||
|
"TRIGGER": "Trigger",
|
||||||
|
"CREATED": "Creation Time",
|
||||||
|
"DESCRIPTION": "Description",
|
||||||
|
"NO_POLICY": "No policy",
|
||||||
|
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||||
|
"DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?",
|
||||||
|
"ENABLED_POLICY_TITLE": "Enable Policy",
|
||||||
|
"DISABLED_POLICY_TITLE": "Disable Policy",
|
||||||
|
"DELETE_POLICY_SUMMARY": "Do you want to delete policy {{names}}?",
|
||||||
|
"EDIT_POLICY": "Edit P2P Provider Policy",
|
||||||
|
"ADD_POLICY": "Create P2P Provider policy",
|
||||||
|
"NAME_REQUIRED": "Name is required",
|
||||||
|
"PROVIDER_REQUIRED": "Provider is required",
|
||||||
|
"ADDED_SUCCESS": "Added policy successfully",
|
||||||
|
"UPDATED_SUCCESS": "Updated policy successfully",
|
||||||
|
"EXECUTE": "Execute",
|
||||||
|
"EXECUTE_SUCCESSFULLY": "Executed successfully",
|
||||||
|
"EXECUTE_TITLE": "Confirm Policy Execution",
|
||||||
|
"EXECUTE_SUMMARY": "Do you want to execute the policy {{param}}?",
|
||||||
|
"STOP_TITLE": "Confirm Execution Stopping",
|
||||||
|
"STOP_SUMMARY": "Do you want to stop executing the policy {{param}}?",
|
||||||
|
"STOP_SUCCESSFULLY": "Stopped execution successfully",
|
||||||
|
"STATUS_MSG": "Status Message",
|
||||||
|
"JOB_PLACEHOLDER": "We couldn't find any execution",
|
||||||
|
"PROVIDER_TYPE": "Vendor",
|
||||||
|
"ID": "Execution ID",
|
||||||
|
"NO_PROVIDER": "Please add a provider first",
|
||||||
|
"EXTRA_ATTRS": "Extra Attrs",
|
||||||
|
"TASKS": "Tasks",
|
||||||
|
"TASKS_PLACEHOLDER": "We couldn't find any task",
|
||||||
|
"SEVERITY_WARNING": "Vulnerability settings here conflicts with the relevant project configuration that will override the settings here",
|
||||||
|
"REPOS": "Repositories",
|
||||||
|
"TAGS": "Tags",
|
||||||
|
"CRITERIA": "Criteria",
|
||||||
|
"ONLY_SIGNED": "Only signed images",
|
||||||
|
"PREHEAT_ON_PUSH": "Preheat on push",
|
||||||
|
"START_TEXT": "No vulnerability severity of",
|
||||||
|
"EDN_TEXT": "and above",
|
||||||
|
"LABELS": "Labels",
|
||||||
|
"SCHEDULE": "Schedule",
|
||||||
|
"TEST_FAILED": "Test failed",
|
||||||
|
"MANUAL": "Manual",
|
||||||
|
"SCHEDULED": "Scheduled",
|
||||||
|
"EVENT_BASED": "Event based",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE1": "The policy will be evaluated whenever the following events are occurred:",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE2": "Artifact is pushed",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE3": "Artifact is labeled",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE4": "Artifact is scanned",
|
||||||
|
"REPO_REQUIRED": "Repository is required",
|
||||||
|
"TAG_REQUIRED": "Tag is required",
|
||||||
|
"DELETE_SUCCESSFULLY": "Deleted policy successfully",
|
||||||
|
"UPDATED_SUCCESSFULLY": "Updated policy successfully",
|
||||||
|
"EXECUTIONS": "Executions",
|
||||||
|
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,or **",
|
||||||
|
"CONTENT_WARNING": "Content trust settings here conflicts with the relevant project configuration that will override the settings here"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
"RUN": "执行",
|
"RUN": "执行",
|
||||||
"CONTINUE": "继续",
|
"CONTINUE": "继续",
|
||||||
"ENABLE": "启用",
|
"ENABLE": "启用",
|
||||||
"DISABLE": "关闭"
|
"DISABLE": "禁用"
|
||||||
},
|
},
|
||||||
"BATCH": {
|
"BATCH": {
|
||||||
"DELETED_SUCCESS": "删除成功",
|
"DELETED_SUCCESS": "删除成功",
|
||||||
@ -867,7 +867,7 @@
|
|||||||
"WEBHOOK_TOOLTIP": "当执行推送,拉动,删除,扫描镜像或 chart 等特定操作时,启用 webhooks 以在指定端点接收回调",
|
"WEBHOOK_TOOLTIP": "当执行推送,拉动,删除,扫描镜像或 chart 等特定操作时,启用 webhooks 以在指定端点接收回调",
|
||||||
"HOURLY_CRON": "每小时运行一次。相当于 0 0 * * * *",
|
"HOURLY_CRON": "每小时运行一次。相当于 0 0 * * * *",
|
||||||
"WEEKLY_CRON": "每周一次,周六/周日午夜之间开始。相当于 0 0 * * * *",
|
"WEEKLY_CRON": "每周一次,周六/周日午夜之间开始。相当于 0 0 * * * *",
|
||||||
"DAILY_CRON": "每天午夜运行一次。相当于 0 0 * * * *"
|
"DAILY_CRON": "每天午夜运行一次。相当于 0 0 0 * * *"
|
||||||
},
|
},
|
||||||
"LDAP": {
|
"LDAP": {
|
||||||
"URL": "LDAP URL",
|
"URL": "LDAP URL",
|
||||||
@ -1493,5 +1493,69 @@
|
|||||||
"SET_DEFAULT_FAILED": "设为默认失败",
|
"SET_DEFAULT_FAILED": "设为默认失败",
|
||||||
"UPDATE_INSTANCE": "更新实例",
|
"UPDATE_INSTANCE": "更新实例",
|
||||||
"CREATE_INSTANCE": "新建实例"
|
"CREATE_INSTANCE": "新建实例"
|
||||||
|
},
|
||||||
|
"P2P_PROVIDER": {
|
||||||
|
"P2P_PROVIDER": "预热",
|
||||||
|
"POLICIES": "策略",
|
||||||
|
"NEW_POLICY": "新建策略",
|
||||||
|
"NAME": "名称",
|
||||||
|
"ENABLED": "启用",
|
||||||
|
"PROVIDER": "提供商",
|
||||||
|
"FILTERS": "过滤器",
|
||||||
|
"TRIGGER": "触发器",
|
||||||
|
"CREATED": "创建时间",
|
||||||
|
"DESCRIPTION": "描述",
|
||||||
|
"NO_POLICY": "暂无记录",
|
||||||
|
"ENABLED_POLICY_SUMMARY": "是否启用策略 {{name}}?",
|
||||||
|
"DISABLED_POLICY_SUMMARY": "是否禁用策略 {{name}}?",
|
||||||
|
"ENABLED_POLICY_TITLE": "启用策略",
|
||||||
|
"DISABLED_POLICY_TITLE": "禁用策略",
|
||||||
|
"DELETE_POLICY_SUMMARY": "是否删除策略 {{names}}?",
|
||||||
|
"EDIT_POLICY": "编辑 P2P 策略",
|
||||||
|
"ADD_POLICY": "创建 P2P 策略",
|
||||||
|
"NAME_REQUIRED": "名称为必填项",
|
||||||
|
"PROVIDER_REQUIRED": "提供商为必填项",
|
||||||
|
"ADDED_SUCCESS": "新增策略成功.",
|
||||||
|
"UPDATED_SUCCESS": "更新策略成功",
|
||||||
|
"EXECUTE": "执行",
|
||||||
|
"EXECUTE_SUCCESSFULLY": "执行成功",
|
||||||
|
"EXECUTE_TITLE": "执行策略确认",
|
||||||
|
"EXECUTE_SUMMARY": "是否执行策略 {{param}}?",
|
||||||
|
"STOP_TITLE": "中止执行策略确认",
|
||||||
|
"STOP_SUMMARY": "是否中止执行策略 {{param}}?",
|
||||||
|
"STOP_SUCCESSFULLY": "中止执行策略成功",
|
||||||
|
"STATUS_MSG": "状态信息",
|
||||||
|
"JOB_PLACEHOLDER": "暂无记录",
|
||||||
|
"PROVIDER_TYPE": "发起方",
|
||||||
|
"ID": "执行 ID",
|
||||||
|
"NO_PROVIDER": "请先添加提供商",
|
||||||
|
"EXTRA_ATTRS": "额外属性",
|
||||||
|
"TASKS": "任务",
|
||||||
|
"TASKS_PLACEHOLDER": "暂无记录",
|
||||||
|
"SEVERITY_WARNING": "此处漏洞等级设置与项目设置冲突且将会被项目设置覆盖",
|
||||||
|
"REPOS": "仓库",
|
||||||
|
"TAGS": "Tags",
|
||||||
|
"CRITERIA": "标准",
|
||||||
|
"ONLY_SIGNED": "仅签名镜像",
|
||||||
|
"PREHEAT_ON_PUSH": "推送时预热",
|
||||||
|
"START_TEXT": "无漏洞等级为",
|
||||||
|
"EDN_TEXT": "及以上",
|
||||||
|
"LABELS": "标签",
|
||||||
|
"SCHEDULE": "定时任务",
|
||||||
|
"TEST_FAILED": "测试失败",
|
||||||
|
"MANUAL": "手动",
|
||||||
|
"SCHEDULED": "定时",
|
||||||
|
"EVENT_BASED": "基于事件",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE1": "当以下事件发生时该策略将会被执行:",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE2": "推送 Artifact",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE3": "为 Artifact 加标签",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE4": "扫描 Artifact",
|
||||||
|
"REPO_REQUIRED": "仓库为必填项",
|
||||||
|
"TAG_REQUIRED": "Tag 为必填项",
|
||||||
|
"DELETE_SUCCESSFULLY": "删除策略成功",
|
||||||
|
"UPDATED_SUCCESSFULLY": "更新策略成功",
|
||||||
|
"EXECUTIONS": "执行记录",
|
||||||
|
"TAG_SEPARATOR": "使用逗号分割 tags,tag* 和 **",
|
||||||
|
"CONTENT_WARNING": "内容信任设置与项目设置冲突且将会被项目设置覆盖"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1480,5 +1480,69 @@
|
|||||||
"SET_DEFAULT_FAILED": "Setting as default failed",
|
"SET_DEFAULT_FAILED": "Setting as default failed",
|
||||||
"UPDATE_INSTANCE": "Update instance",
|
"UPDATE_INSTANCE": "Update instance",
|
||||||
"CREATE_INSTANCE": "Create instance"
|
"CREATE_INSTANCE": "Create instance"
|
||||||
|
},
|
||||||
|
"P2P_PROVIDER": {
|
||||||
|
"P2P_PROVIDER": "Preheat",
|
||||||
|
"POLICIES": "Policies",
|
||||||
|
"NEW_POLICY": "NEW POLICY",
|
||||||
|
"NAME": "Name",
|
||||||
|
"ENABLED": "Enabled",
|
||||||
|
"PROVIDER": "Provider",
|
||||||
|
"FILTERS": "Filters",
|
||||||
|
"TRIGGER": "Trigger",
|
||||||
|
"CREATED": "Creation Time",
|
||||||
|
"DESCRIPTION": "Description",
|
||||||
|
"NO_POLICY": "No policy",
|
||||||
|
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||||
|
"DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?",
|
||||||
|
"ENABLED_POLICY_TITLE": "Enable Policy",
|
||||||
|
"DISABLED_POLICY_TITLE": "Disable Policy",
|
||||||
|
"DELETE_POLICY_SUMMARY": "Do you want to delete policy {{names}}?",
|
||||||
|
"EDIT_POLICY": "Edit P2P Provider Policy",
|
||||||
|
"ADD_POLICY": "Create P2P Provider policy",
|
||||||
|
"NAME_REQUIRED": "Name is required",
|
||||||
|
"PROVIDER_REQUIRED": "Provider is required",
|
||||||
|
"ADDED_SUCCESS": "Added policy successfully",
|
||||||
|
"UPDATED_SUCCESS": "Updated policy successfully",
|
||||||
|
"EXECUTE": "Execute",
|
||||||
|
"EXECUTE_SUCCESSFULLY": "Executed successfully",
|
||||||
|
"EXECUTE_TITLE": "Confirm Policy Execution",
|
||||||
|
"EXECUTE_SUMMARY": "Do you want to execute the policy {{param}}?",
|
||||||
|
"STOP_TITLE": "Confirm Execution Stopping",
|
||||||
|
"STOP_SUMMARY": "Do you want to stop executing the policy {{param}}?",
|
||||||
|
"STOP_SUCCESSFULLY": "Stopped execution successfully",
|
||||||
|
"STATUS_MSG": "Status Message",
|
||||||
|
"JOB_PLACEHOLDER": "We couldn't find any execution",
|
||||||
|
"PROVIDER_TYPE": "Vendor",
|
||||||
|
"ID": "Execution ID",
|
||||||
|
"NO_PROVIDER": "Please add a provider first",
|
||||||
|
"EXTRA_ATTRS": "Extra Attrs",
|
||||||
|
"TASKS": "Tasks",
|
||||||
|
"TASKS_PLACEHOLDER": "We couldn't find any task",
|
||||||
|
"SEVERITY_WARNING": "Vulnerability settings here conflicts with the relevant project configuration that will override the settings here",
|
||||||
|
"REPOS": "Repositories",
|
||||||
|
"TAGS": "Tags",
|
||||||
|
"CRITERIA": "Criteria",
|
||||||
|
"ONLY_SIGNED": "Only signed images",
|
||||||
|
"PREHEAT_ON_PUSH": "Preheat on push",
|
||||||
|
"START_TEXT": "No vulnerability severity of",
|
||||||
|
"EDN_TEXT": "and above",
|
||||||
|
"LABELS": "Labels",
|
||||||
|
"SCHEDULE": "Schedule",
|
||||||
|
"TEST_FAILED": "Test failed",
|
||||||
|
"MANUAL": "Manual",
|
||||||
|
"SCHEDULED": "Scheduled",
|
||||||
|
"EVENT_BASED": "Event based",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE1": "The policy will be evaluated whenever the following events are occurred:",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE2": "Artifact is pushed",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE3": "Artifact is labeled",
|
||||||
|
"EVENT_BASED_EXPLAIN_LINE4": "Artifact is scanned",
|
||||||
|
"REPO_REQUIRED": "Repository is required",
|
||||||
|
"TAG_REQUIRED": "Tag is required",
|
||||||
|
"DELETE_SUCCESSFULLY": "Deleted policy successfully",
|
||||||
|
"UPDATED_SUCCESSFULLY": "Updated policy successfully",
|
||||||
|
"EXECUTIONS": "Executions",
|
||||||
|
"TAG_SEPARATOR": "Enter multiple comma separated tags,tag*,or **",
|
||||||
|
"CONTENT_WARNING": "Content trust settings here conflicts with the relevant project configuration that will override the settings here"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<div class="normal-wrapper-box" *ngIf="!isEditMode">
|
<ng-container *ngIf="!isInlineModel">
|
||||||
|
<div class="normal-wrapper-box" *ngIf="!isEditMode">
|
||||||
<span class="font-style" [style.width]="labelWidth">{{ labelCurrent | translate }}</span>
|
<span class="font-style" [style.width]="labelWidth">{{ labelCurrent | translate }}</span>
|
||||||
<span>{{(originScheduleType ? 'SCHEDULE.'+ originScheduleType.toUpperCase(): "") | translate}}</span>
|
<span>{{(originScheduleType ? 'SCHEDULE.'+ originScheduleType.toUpperCase(): "") | translate}}</span>
|
||||||
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.HOURLY" href="javascript:void(0)" role="tooltip"
|
<a [hidden]="originScheduleType!==SCHEDULE_TYPE.HOURLY" href="javascript:void(0)" role="tooltip"
|
||||||
@ -22,8 +23,8 @@
|
|||||||
id="editSchedule">
|
id="editSchedule">
|
||||||
{{ "BUTTON.EDIT" | translate }}
|
{{ "BUTTON.EDIT" | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="normal-wrapper-box" *ngIf="isEditMode">
|
<div class="normal-wrapper-box" *ngIf="isEditMode">
|
||||||
<span class="font-style" [style.width]="labelWidth">{{ labelEdit | translate }}</span>
|
<span class="font-style" [style.width]="labelWidth">{{ labelEdit | translate }}</span>
|
||||||
<div class="select select-schedule clr-select-wrapper">
|
<div class="select select-schedule clr-select-wrapper">
|
||||||
<select name="selectPolicy" id="selectPolicy" [(ngModel)]="scheduleType">
|
<select name="selectPolicy" id="selectPolicy" [(ngModel)]="scheduleType">
|
||||||
@ -56,4 +57,64 @@
|
|||||||
<button class="btn btn-link " (click)="isEditMode=false">
|
<button class="btn btn-link " (click)="isEditMode=false">
|
||||||
{{ "BUTTON.CANCEL" | translate }}
|
{{ "BUTTON.CANCEL" | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="isInlineModel">
|
||||||
|
<div class="normal-wrapper-box height-1rem" *ngIf="!isEditMode">
|
||||||
|
<span>{{(originScheduleType ? 'SCHEDULE.'+ originScheduleType.toUpperCase(): "") | translate}}</span>
|
||||||
|
<a *ngIf="originScheduleType===SCHEDULE_TYPE.HOURLY" href="javascript:void(0)" role="tooltip"
|
||||||
|
aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||||
|
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||||
|
<span class="tooltip-content">{{'CONFIG.TOOLTIP.HOURLY_CRON' | translate}}</span>
|
||||||
|
</a>
|
||||||
|
<a *ngIf="originScheduleType===SCHEDULE_TYPE.WEEKLY" href="javascript:void(0)" role="tooltip"
|
||||||
|
aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||||
|
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||||
|
<span class="tooltip-content">{{'CONFIG.TOOLTIP.WEEKLY_CRON' | translate}}</span>
|
||||||
|
</a>
|
||||||
|
<a *ngIf="originScheduleType===SCHEDULE_TYPE.DAILY" href="javascript:void(0)" role="tooltip" aria-haspopup="true"
|
||||||
|
class="tooltip tooltip-top-right">
|
||||||
|
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||||
|
<span class="tooltip-content">{{'CONFIG.TOOLTIP.DAILY_CRON' | translate}}</span>
|
||||||
|
</a>
|
||||||
|
<span *ngIf="originScheduleType===SCHEDULE_TYPE.CUSTOM">{{ "SCHEDULE.CRON" | translate }} :</span>
|
||||||
|
<span *ngIf="originScheduleType===SCHEDULE_TYPE.CUSTOM">{{ oriCron }}</span>
|
||||||
|
<button [disabled]="disabled" class="btn btn-link" (click)="editSchedule()"
|
||||||
|
id="inline-edit">
|
||||||
|
{{ "BUTTON.EDIT" | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="normal-wrapper-box height-1rem" *ngIf="isEditMode">
|
||||||
|
<div class="select select-schedule clr-select-wrapper">
|
||||||
|
<select name="selectPolicy" id="inline-select" [(ngModel)]="scheduleType">
|
||||||
|
<option [value]="SCHEDULE_TYPE.NONE">{{'SCHEDULE.NONE' | translate}}</option>
|
||||||
|
<option [value]="SCHEDULE_TYPE.HOURLY">{{'SCHEDULE.HOURLY' | translate}}</option>
|
||||||
|
<option [value]="SCHEDULE_TYPE.DAILY">{{'SCHEDULE.DAILY' | translate}}</option>
|
||||||
|
<option [value]="SCHEDULE_TYPE.WEEKLY">{{'SCHEDULE.WEEKLY' | translate}}</option>
|
||||||
|
<option [value]="SCHEDULE_TYPE.CUSTOM">{{'SCHEDULE.CUSTOM' | translate}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div [ngClass]="{'mt-1': dateInvalid}" *ngIf="scheduleType===SCHEDULE_TYPE.CUSTOM" class="clr-control-container" [class.clr-error]="dateInvalid">
|
||||||
|
<div class="clr-input-wrapper">
|
||||||
|
<label class="required ml-1">{{ "SCHEDULE.CRON" | translate }}</label>
|
||||||
|
<input autocomplete="off" type="text" (blur)="blurInvalid()" (input)="inputInvalid()" name="targetCron" id="inline-target"
|
||||||
|
#cronStringInput="ngModel" required class="clr-input" [(ngModel)]="cronString">
|
||||||
|
<clr-tooltip>
|
||||||
|
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||||
|
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||||
|
<cron-tooltip></cron-tooltip>
|
||||||
|
</clr-tooltip-content>
|
||||||
|
</clr-tooltip>
|
||||||
|
</div>
|
||||||
|
<clr-control-error class="ml-1" *ngIf="dateInvalid">{{'TOOLTIP.CRON_REQUIRED' | translate}}</clr-control-error>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="isEditMode" class="mt-05">
|
||||||
|
<button class="btn btn-link margin-left-minus-075" (click)="save()" id="inline-config-save">
|
||||||
|
{{ "BUTTON.SAVE" | translate }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-link" (click)="isEditMode=false">
|
||||||
|
{{ "BUTTON.CANCEL" | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
@ -18,3 +18,12 @@ span.required {
|
|||||||
margin-left: .25rem;
|
margin-left: .25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.height-1rem {
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
.margin-left-minus-075{
|
||||||
|
margin-left: -0.75rem;
|
||||||
|
}
|
||||||
|
.mt-05 {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ const SCHEDULE_TYPE = {
|
|||||||
styleUrls: ["./cron-schedule.component.scss"]
|
styleUrls: ["./cron-schedule.component.scss"]
|
||||||
})
|
})
|
||||||
export class CronScheduleComponent implements OnChanges {
|
export class CronScheduleComponent implements OnChanges {
|
||||||
|
@Input() isInlineModel: boolean = false;
|
||||||
@Input() originCron: OriginCron;
|
@Input() originCron: OriginCron;
|
||||||
@Input() labelEdit: string;
|
@Input() labelEdit: string;
|
||||||
@Input() labelCurrent: string;
|
@Input() labelCurrent: string;
|
||||||
|
@ -176,6 +176,16 @@ export const USERSTATICPERMISSION = {
|
|||||||
"UPDATE": "update",
|
"UPDATE": "update",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"P2P_PROVIDER": {
|
||||||
|
"KEY": "preheat-policy",
|
||||||
|
"VALUE": {
|
||||||
|
"LIST": "list",
|
||||||
|
"READ": "read",
|
||||||
|
"CREATE": "create",
|
||||||
|
"UPDATE": "update",
|
||||||
|
"DELETE": "delete"
|
||||||
|
}
|
||||||
|
},
|
||||||
"SCANNER": {
|
"SCANNER": {
|
||||||
"KEY": "scanner",
|
"KEY": "scanner",
|
||||||
"VALUE": {
|
"VALUE": {
|
||||||
|
@ -630,3 +630,18 @@ export function dbEncodeURIComponent(url: string) {
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete empty key
|
||||||
|
* @param obj
|
||||||
|
*/
|
||||||
|
export function deleteEmptyKey(obj: Object): void {
|
||||||
|
if (isEmptyObject(obj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for ( let key in obj ) {
|
||||||
|
if ( !obj[key] ) {
|
||||||
|
delete obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user