Merge pull request #11027 from AllForNothing/webhook

Improve Webhook UI
This commit is contained in:
Will Sun 2020-03-11 18:35:29 +08:00 committed by GitHub
commit aa73f16a20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 869 additions and 242 deletions

View File

@ -63,6 +63,7 @@ import {
import { RepositoryDefaultService, RepositoryService } from "./repository/repository.service";
import { ArtifactDefaultService, ArtifactService } from "./repository/artifact/artifact.service";
import { GridViewComponent } from "./repository/gridview/grid-view.component";
import { LastTriggerComponent } from "./webhook/last-trigger/last-trigger.component";
@NgModule({
imports: [
@ -110,6 +111,7 @@ import { GridViewComponent } from "./repository/gridview/grid-view.component";
ValuesComponent,
ArtifactVulnerabilitiesComponent,
GridViewComponent,
LastTriggerComponent
],
exports: [ProjectComponent, ListProjectComponent],
providers: [

View File

@ -12,7 +12,7 @@
</div>
<ng-container *ngIf="scanner">
<div class="clr-form-control">
<label for="select-full" class="clr-control-label name">{{'SCANNER.NAME' | translate}}</label>
<label class="clr-control-label name">{{'SCANNER.NAME' | translate}}</label>
<div class="clr-control-container">
<div class="clr-input-wrapper">
<div class="clr-input-wrapper name">

View File

@ -2,13 +2,57 @@
<inline-alert class="modal-title"></inline-alert>
<form #webhookForm="ngForm" class="clr-form clr-form-horizontal">
<section class="form-block webhook-section">
<!-- name -->
<div class="clr-form-control">
<label for="edit_endpoint_url" class="clr-control-label required">{{'WEBHOOK.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 autocomplete="off" class="clr-input" type="text" id="name" [disabled]="checking" [(ngModel)]="webhook.name"
size="30" name="notify-type" #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">
{{'WEBHOOK.NAME_REQUIRED' | translate}}
</clr-control-error>
</div>
</div>
<!-- description -->
<div class="clr-form-control">
<label for="edit_endpoint_url" class="clr-control-label">{{'WEBHOOK.DESCRIPTION' | translate}}</label>
<div class="clr-control-container">
<textarea autocomplete="off" class="clr-textarea width-238" type="text" id="description" [disabled]="checking" [(ngModel)]="webhook.description"
name="description"></textarea>
</div>
</div>
<!-- notify type -->
<clr-select-container>
<label class="required">{{'WEBHOOK.NOTIFY_TYPE' | translate}}</label>
<select clrSelect name="notifyType" id="notify_type" [(ngModel)]="webhook.targets[0].type" [disabled]="checking">
<option *ngFor="let type of metadata?.notify_type" value="{{type}}">{{type}}</option>
</select>
</clr-select-container>
<div class="clr-form-control">
<label class="clr-control-label required">{{'WEBHOOK.EVENT_TYPE' | translate}}</label>
<div class="clr-control-container clr-control-inline" [class.clr-error]="!hasEventType()">
<div class="clr-checkbox-wrapper" *ngFor="let item of metadata?.event_type">
<input type="checkbox" id="{{item}}" name="eventTypes" value="{{item}}" class="clr-checkbox" (change)="setEventType(item)" [checked]="getEventType(item)">
<label for="{{item}}" class="clr-control-label">{{item}}</label>
</div>
<div class="clr-subtext-wrapper" *ngIf="!hasEventType()">
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
<span class="clr-subtext">{{'WEBHOOK.EVENT_TYPE_REQUIRED' | translate}}</span>
</div>
</div>
</div>
<!-- endpoint URL -->
<div class="clr-form-control">
<label for="edit_endpoint_url" class="clr-control-label required">{{'WEBHOOK.ENDPOINT_URL' | translate}}</label>
<div class="clr-control-container" [class.clr-error]="enpointURL.errors && enpointURL.errors.required && (enpointURL.dirty || enpointURL.touched)">
<div class="clr-input-wrapper">
<input class="clr-input" type="text" id="edit_endpoint_url" [disabled]="checking" [(ngModel)]="webhookTarget.address"
size="30" name="edit_endpoint_url" #enpointURL="ngModel" required placeholder="http(s)://192.168.1.1">
<input autocomplete="off" class="clr-input" type="text" id="edit_endpoint_url" [disabled]="checking" [(ngModel)]="webhook.targets[0].address"
size="30" name="edit_endpoint_url" #enpointURL="ngModel" required [placeholder]="webhook.targets[0].type ==='http'?'http(s)://192.168.1.1':''">
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
</div>
<clr-control-error *ngIf="enpointURL.errors && enpointURL.errors.required && (enpointURL.dirty || enpointURL.touched)" class="tooltip-content">
@ -17,16 +61,18 @@
</div>
</div>
<!-- auth_header -->
<div class="clr-form-control">
<div class="clr-form-control" *ngIf="webhook?.targets[0]?.type ==='http'">
<label for="auth_header" class="clr-control-label">{{ 'WEBHOOK.AUTH_HEADER' |
translate }}</label>
<div class="clr-control-container">
<div class="clr-input-wrapper">
<input class="clr-input" type="text" id="auth_header" [disabled]="checking"
[(ngModel)]="webhookTarget.auth_header" size="30" name="auth_header">
<input autocomplete="off" class="clr-input" type="text" id="auth_header" [disabled]="checking"
[(ngModel)]="webhook.targets[0].auth_header" size="30" name="auth_header">
</div>
</div>
</div>
<!-- verify remote cert -->
<div class="clr-form-control">
<label for="verify_remote_cert" class="clr-control-label">
@ -40,17 +86,18 @@
</label>
<div class="clr-control-container padding-top-3">
<input type="checkbox" [disabled]="checking" clrCheckbox name="verify_remote_cert" id="verify_remote_cert"
(ngModelChange)="setCertValue($event)" [ngModel]="!webhookTarget.skip_cert_verify"/> </div>
(ngModelChange)="setCertValue($event)" [ngModel]="!webhook?.targets[0]?.skip_cert_verify"/> </div>
</div>
</section>
</form>
<div class="mt-1" *ngIf="!isModify">
<button type="button" id="new-webhook-continue" class="btn btn-primary" [disabled]="!isValid" (click)="onSubmit()">{{'BUTTON.CONTINUE' | translate}}</button>
<button type="button" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
<button type="button" id="webhook-test-add" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
<button type="button" id="new-webhook-continue" class="btn btn-primary" [disabled]="!isValid" (click)="add()">{{'BUTTON.ADD' | translate}}</button>
<button type="button" class="btn btn-outline" id="add-webhook-cancel" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
</div>
<div class="mt-1 bottom-btn" *ngIf="isModify">
<button type="button" [clrLoading]="checkBtnState" class="btn btn-outline" id="webhook-test" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
<button type="button" class="btn btn-primary" id="edit-webhook-save" [disabled]="!isValid" (click)="save()">{{'BUTTON.SAVE' | translate}}</button>
<button type="button" class="btn btn-outline" id="edit-webhook-cancel" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" id="edit-webhook-save" [disabled]="!isValid" (click)="onSubmit()">{{'BUTTON.SAVE' | translate}}</button>
</div>
</div>

View File

@ -7,4 +7,7 @@
.bottom-btn {
text-align: right;
margin-right: 3.4rem;
}
.width-238 {
width: 238px;
}

View File

@ -1,7 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { AddWebhookFormComponent } from './add-webhook-form.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
@ -10,6 +9,8 @@ import { FormsModule } from '@angular/forms';
import { WebhookService } from "../webhook.service";
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
import { of } from 'rxjs';
import { Webhook } from "../webhook";
import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component";
describe('AddWebhookFormComponent', () => {
let component: AddWebhookFormComponent;
@ -17,17 +18,60 @@ describe('AddWebhookFormComponent', () => {
const mockWebhookService = {
getCurrentUser: () => {
return of(null);
},
createWebhook() {
return of(null);
},
editWebhook() {
return of(null);
},
testEndpoint() {
return of(null);
}
};
const mockMessageHandlerService = {
handleError: () => { }
};
const mockedWehook: Webhook = {
id: 1,
project_id: 1,
name: 'test',
description: 'just a test webhook',
targets: [{
address: 'https://test.com',
type: 'http',
attachment: null,
auth_header: null,
skip_cert_verify: true,
}],
event_types: [
'projectQuota'
],
creator: null,
creation_time: null,
update_time: null,
enabled: true,
};
const mockedMetadata = {
"event_type": [
"projectQuota",
"pullImage",
"scanningFailed",
"uploadChart",
"deleteChart",
"downloadChart",
"scanningCompleted",
"pushImage",
"deleteImage"
],
"notify_type": [
"http",
"slack"
]
};
beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
imports: [
BrowserAnimationsModule,
ClarityModule,
@ -37,7 +81,9 @@ describe('AddWebhookFormComponent', () => {
NoopAnimationsModule,
HttpClientTestingModule
],
declarations: [AddWebhookFormComponent],
declarations: [AddWebhookFormComponent,
InlineAlertComponent,
],
providers: [
TranslateService,
{ provide: WebhookService, useValue: mockWebhookService },
@ -52,10 +98,51 @@ describe('AddWebhookFormComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(AddWebhookFormComponent);
component = fixture.componentInstance;
component.metadata = mockedMetadata;
fixture.detectChanges();
});
it('should create', () => {
it('should create', async () => {
expect(component).toBeTruthy();
const cancelButtonForAdd: HTMLButtonElement = fixture.nativeElement.querySelector("#add-webhook-cancel");
expect(cancelButtonForAdd).toBeTruthy();
component.isModify = true;
fixture.detectChanges();
await fixture.whenStable();
const cancelButtonForEdit: HTMLButtonElement = fixture.nativeElement.querySelector("#edit-webhook-cancel");
expect(cancelButtonForEdit).toBeTruthy();
});
it("should occur a 'name is required' error", async () => {
await fixture.whenStable();
fixture.autoDetectChanges(true);
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();
nameInput.dispatchEvent(new Event('blur'));
const errorEle: HTMLElement = fixture.nativeElement.querySelector("clr-control-error");
expect(errorEle.innerText).toEqual('WEBHOOK.NAME_REQUIRED');
});
it("test button should work", async () => {
const spy: jasmine.Spy = spyOn(component, 'onTestEndpoint').and.returnValue(undefined);
const testButton: HTMLButtonElement = fixture.nativeElement.querySelector("#webhook-test-add");
testButton.dispatchEvent(new Event('click'));
fixture.detectChanges();
await fixture.whenStable();
expect(spy.calls.count()).toEqual(1);
});
it("add button should work", async () => {
const spy: jasmine.Spy = spyOn(component, 'add').and.returnValue(undefined);
component.webhook = mockedWehook;
fixture.detectChanges();
await fixture.whenStable();
expect(component.isValid).toBeTruthy();
const addButton: HTMLButtonElement = fixture.nativeElement.querySelector("#new-webhook-continue");
addButton.dispatchEvent(new Event('click'));
fixture.detectChanges();
await fixture.whenStable();
expect(spy.calls.count()).toEqual(1);
});
});

View File

@ -1,89 +1,62 @@
import {
Component,
OnInit,
OnChanges,
Input,
ViewChild,
Output,
EventEmitter,
SimpleChanges
} from "@angular/core";
import { Webhook, Target } from "../webhook";
import { Webhook } from "../webhook";
import { NgForm } from "@angular/forms";
import { ClrLoadingState } from "@clr/angular";
import { finalize } from "rxjs/operators";
import { WebhookService } from "../webhook.service";
import { WebhookEventTypes } from '../../../shared/shared.const';
import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component";
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
import { TranslateService } from "@ngx-translate/core";
@Component({
selector: 'add-webhook-form',
templateUrl: './add-webhook-form.component.html',
styleUrls: ['./add-webhook-form.component.scss']
})
export class AddWebhookFormComponent implements OnInit, OnChanges {
export class AddWebhookFormComponent implements OnInit {
closable: boolean = true;
staticBackdrop: boolean = true;
checking: boolean = false;
checkBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
webhookForm: NgForm;
submitting: boolean = false;
webhookTarget: Target = new Target();
@Input() projectId: number;
@Input() webhook: Webhook;
@Input() isModify: boolean;
webhook: Webhook = new Webhook();
isModify: boolean;
@Input() isOpen: boolean;
@Output() edit = new EventEmitter<boolean>();
@Output() close = new EventEmitter<boolean>();
@ViewChild("webhookForm", { static: true }) currentForm: NgForm;
@ViewChild(InlineAlertComponent, { static: false }) inlineAlert: InlineAlertComponent;
@Input()
metadata: any;
@Output() notify = new EventEmitter<Webhook>();
constructor(
private webhookService: WebhookService,
private messageHandlerService: MessageHandlerService,
private translate: TranslateService
) { }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges) {
if (changes['isOpen'] && changes['isOpen'].currentValue) {
Object.assign(this.webhookTarget, this.webhook.targets[0]);
}
}
onTestEndpoint() {
this.checkBtnState = ClrLoadingState.LOADING;
this.checking = true;
this.webhookService
.testEndpoint(this.projectId, {
targets: [this.webhookTarget]
targets: this.webhook.targets
})
.pipe(finalize(() => (this.checking = false)))
.subscribe(
response => {
if (this.isModify) {
this.inlineAlert.showInlineSuccess({
message: "WEBHOOK.TEST_ENDPOINT_SUCCESS"
});
} else {
this.translate.get("WEBHOOK.TEST_ENDPOINT_SUCCESS").subscribe((res: string) => {
this.messageHandlerService.info(res);
});
}
this.inlineAlert.showInlineSuccess({message: "WEBHOOK.TEST_ENDPOINT_SUCCESS"});
this.checkBtnState = ClrLoadingState.SUCCESS;
},
error => {
if (this.isModify) {
this.inlineAlert.showInlineError("WEBHOOK.TEST_ENDPOINT_FAILURE");
} else {
this.messageHandlerService.handleError(error);
}
this.inlineAlert.showInlineError("WEBHOOK.TEST_ENDPOINT_FAILURE");
this.checkBtnState = ClrLoadingState.DEFAULT;
}
);
@ -95,30 +68,38 @@ export class AddWebhookFormComponent implements OnInit, OnChanges {
this.inlineAlert.close();
}
onSubmit() {
const rx = this.isModify
? this.webhookService.editWebhook(this.projectId, this.webhook.id, Object.assign(this.webhook, { targets: [this.webhookTarget] }))
: this.webhookService.createWebhook(this.projectId, {
targets: [this.webhookTarget],
event_types: Object.keys(WebhookEventTypes).map(key => WebhookEventTypes[key]),
enabled: true,
});
rx.pipe(finalize(() => (this.submitting = false)))
add() {
this.submitting = true;
this.webhookService.createWebhook(this.projectId, this.webhook)
.pipe(finalize(() => (this.submitting = false)))
.subscribe(
response => {
this.edit.emit(this.isModify);
this.notify.emit();
this.inlineAlert.close();
},
error => {
this.isModify
? this.inlineAlert.showInlineError(error)
: this.messageHandlerService.handleError(error);
this.inlineAlert.showInlineError(error);
}
);
}
save() {
this.submitting = true;
this.webhookService.editWebhook(this.projectId, this.webhook.id, this.webhook)
.pipe(finalize(() => (this.submitting = false)))
.subscribe(
response => {
this.inlineAlert.close();
this.notify.emit();
},
error => {
this.inlineAlert.showInlineError(error);
}
);
}
setCertValue($event: any): void {
this.webhookTarget.skip_cert_verify = !$event;
this.webhook.targets[0].skip_cert_verify = !$event;
}
public get isValid(): boolean {
@ -126,7 +107,26 @@ export class AddWebhookFormComponent implements OnInit, OnChanges {
this.currentForm &&
this.currentForm.valid &&
!this.submitting &&
!this.checking
!this.checking &&
this.hasEventType()
);
}
setEventType(eventType) {
if (this.webhook.event_types.indexOf(eventType) === -1) {
this.webhook.event_types.push(eventType);
} else {
this.webhook.event_types.splice(this.webhook.event_types.findIndex(item => item === eventType), 1);
}
}
getEventType(eventType): boolean {
return eventType && this.webhook.event_types.indexOf(eventType) !== -1;
}
hasEventType(): boolean {
return this.metadata
&& this.metadata.event_type
&& this.metadata.event_type.length > 0
&& this.webhook.event_types
&& this.webhook.event_types.length > 0;
}
}

View File

@ -1,13 +1,12 @@
<clr-modal [(clrModalOpen)]="isOpen" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
<h3 class="modal-title">{{'WEBHOOK.EDIT_WEBHOOK' | translate}}</h3>
<h3 *ngIf="isEdit" class="modal-title">{{'WEBHOOK.EDIT_WEBHOOK' | translate}}</h3>
<h3 *ngIf="!isEdit" class="modal-title">{{'WEBHOOK.ADD_WEBHOOK' | translate}}</h3>
<div class="modal-body">
<div>{{'WEBHOOK.EDIT_WEBHOOK_DESC' | translate}}</div>
<add-webhook-form [projectId]="projectId"
[isModify]="true"
[isOpen]="isOpen"
[webhook]="webhook"
(edit)="closeModal($event)"
(close)="closeModal($event)"
<add-webhook-form [metadata]="metadata" [projectId]="projectId"
[isOpen]="isOpen"
(close)="closeModal()"
(notify)="notifySuccess()"
></add-webhook-form>
</div>
</clr-modal>

View File

@ -1,7 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { AddWebhookComponent } from './add-webhook.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/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';
@ -14,7 +14,8 @@ describe('AddWebhookComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [
CUSTOM_ELEMENTS_SCHEMA
CUSTOM_ELEMENTS_SCHEMA,
NO_ERRORS_SCHEMA
],
imports: [
BrowserAnimationsModule,
@ -42,4 +43,14 @@ describe('AddWebhookComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
it('should open modal and should be edit model', async () => {
component.isEdit = true;
component.isOpen = true;
fixture.detectChanges();
await fixture.whenStable();
const body: HTMLElement = fixture.nativeElement.querySelector(".modal-body");
expect(body).toBeTruthy();
const title: HTMLElement = fixture.nativeElement.querySelector(".modal-title");
expect(title.innerText).toEqual('WEBHOOK.EDIT_WEBHOOK');
});
});

View File

@ -15,16 +15,18 @@ import { AddWebhookFormComponent } from "../add-webhook-form/add-webhook-form.co
styleUrls: ['./add-webhook.component.scss']
})
export class AddWebhookComponent implements OnInit {
isEdit: boolean;
isOpen: boolean = false;
closable: boolean = false;
staticBackdrop: boolean = true;
@Input() projectId: number;
@Input() webhook: Webhook;
@Output() modify = new EventEmitter<boolean>();
webhook: Webhook;
@Input()
metadata: any;
@ViewChild(AddWebhookFormComponent, { static: false })
addWebhookFormComponent: AddWebhookFormComponent;
@Output() notify = new EventEmitter<Webhook>();
constructor() { }
@ -38,12 +40,11 @@ export class AddWebhookComponent implements OnInit {
onCancel() {
this.isOpen = false;
}
closeModal(isModified: boolean): void {
if (isModified) {
this.modify.emit(true);
}
notifySuccess() {
this.isOpen = false;
this.notify.emit();
}
closeModal() {
this.isOpen = false;
}
}

View File

@ -0,0 +1,29 @@
<h4>{{'WEBHOOK.LAST_TRIGGER' | translate}}</h4>
<clr-datagrid>
<clr-dg-column [clrDgField]="'event_type'">{{'WEBHOOK.TYPE' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.STATUS' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.LAST_TRIGGERED' | translate}}</clr-dg-column>
<clr-dg-row *clrDgItems="let item of lastTriggers">
<clr-dg-cell>{{item.event_type}}</clr-dg-cell>
<clr-dg-cell [ngSwitch]="item.enabled">
<div *ngSwitchCase="true" class="icon-wrap">
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
<span>{{'WEBHOOK.ENABLED' | translate}}</span>
</div>
<div *ngSwitchCase="false" class="icon-wrap">
<clr-icon shape="exclamation-triangle" size="20" class="is-warning"></clr-icon>
<span>{{'WEBHOOK.DISABLED' | translate}}</span>
</div>
</clr-dg-cell>
<clr-dg-cell>{{item.last_trigger_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-placeholder>
{{'WEBHOOK.NO_TRIGGER' | translate}}
</clr-dg-placeholder>
<clr-dg-footer>
<span *ngIf="lastTriggers.length">1 - {{lastTriggers.length}} {{'WEBHOOK.OF' | translate}} </span> {{lastTriggers.length}} {{'WEBHOOK.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="10"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -0,0 +1,57 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { ClarityModule } from "@clr/angular";
import { SharedModule } from "../../../shared/shared.module";
import { LastTriggerComponent } from "./last-trigger.component";
import { LastTrigger } from "../webhook";
import { SimpleChange } from "@angular/core";
describe('LastTriggerComponent', () => {
const mokedTriggers: LastTrigger[] = [
{
policy_name: 'http',
enabled: true,
event_type: 'pullImage',
creation_time: null,
last_trigger_time: null
},
{
policy_name: 'slack',
enabled: true,
event_type: 'pullImage',
creation_time: null,
last_trigger_time: null
}
];
let component: LastTriggerComponent;
let fixture: ComponentFixture<LastTriggerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
BrowserAnimationsModule,
ClarityModule,
],
declarations: [
LastTriggerComponent
],
});
});
beforeEach(() => {
fixture = TestBed.createComponent(LastTriggerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render one row', async () => {
component.inputLastTriggers = mokedTriggers;
component.webhookName = 'slack';
component.ngOnChanges({inputLastTriggers: new SimpleChange([], mokedTriggers, true)});
fixture.detectChanges();
await fixture.whenStable();
const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row');
expect(rows.length).toEqual(1);
});
});

View File

@ -0,0 +1,31 @@
import {
Component, Input, OnChanges,
OnInit, SimpleChanges
} from "@angular/core";
import { LastTrigger } from "../webhook";
@Component({
selector: 'last-trigger',
templateUrl: 'last-trigger.component.html',
styleUrls: ['./last-trigger.component.scss']
})
export class LastTriggerComponent implements OnInit , OnChanges {
@Input() inputLastTriggers: LastTrigger[];
@Input() webhookName: string;
lastTriggers: LastTrigger[] = [];
constructor() {
}
ngOnChanges(changes: SimpleChanges): void {
if (changes && changes['inputLastTriggers']) {
this.lastTriggers = [];
this.inputLastTriggers.forEach(item => {
if (this.webhookName === item.policy_name) {
this.lastTriggers.push(item);
}
});
}
}
ngOnInit(): void {
}
}

View File

@ -1,54 +1,109 @@
<div *ngIf="loadingWebhook" class="clr-row mt-2 center">
<span class="spinner spinner-md"></span>
</div>
<div class="row" *ngIf="!loadingWebhook">
<div *ngIf="!showCreate">
<div>
<div class="row flex-items-xs-between rightPos">
<div class="flex-xs-middle option-left">
<div>
<span class="endpoint-label">Webhook endpoint</span>: {{endpoint}}
<button class="btn btn-link" id="edit-webhook" (click)="openAddWebhookModal()">{{'WEBHOOK.EDIT_BUTTON' | translate}}</button>
</div>
<div [ngSwitch]="isEnabled">
<button *ngSwitchCase="false" id="enable-webhook-action" class="btn btn-link" (click)="switchWebhookStatus(true)">{{'WEBHOOK.ENABLED_BUTTON' | translate}}</button>
<button *ngSwitchCase="true" id="disable-webhook-action" class="btn btn-link disabled-btn" (click)="switchWebhookStatus(false)">{{'WEBHOOK.DISABLED_BUTTON' | translate}}</button>
</div>
<div class="row">
<h4 class="mt-1">{{'WEBHOOK.WEBHOOKS' | translate}}</h4>
<clr-datagrid [clrDgLoading]="loadingWebhookList || loadingMetadata" [(clrDgSelected)]="selectedRow">
<clr-dg-action-bar>
<div class="clr-row">
<div class="clr-col-7">
<button type="button" class="btn btn-secondary" (click)="newWebhook()">
<clr-icon shape="plus" size="16"></clr-icon>
{{'WEBHOOK.NEW_WEBHOOK' | translate}}
</button>
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="btn btn-link" clrDropdownTrigger>
<span id="action-scanner">{{'MEMBER.ACTION' | translate}}
<clr-icon class="clr-icon" shape="caret down"></clr-icon></span>
<clr-dropdown-menu *clrIfOpen>
<button clrDropdownItem (click)="switchWebhookStatus()"
[disabled]="!(selectedRow && selectedRow.length === 1)">
<span *ngIf="selectedRow[0] && !selectedRow[0].enabled">
<clr-icon class="margin-top-2" size="16" shape="success-standard"></clr-icon>
<span class="margin-left-10">{{'WEBHOOK.ENABLE' | translate}}</span>
</span>
<span *ngIf="!(selectedRow[0] && !selectedRow[0].enabled)">
<clr-icon class="margin-top-2" size="16" shape="ban"></clr-icon>
<span class="margin-left-10">{{'WEBHOOK.DISABLE' | translate}}</span>
</span>
</button>
<button clrDropdownItem (click)="editWebhook()"
class="btn btn-secondary" [disabled]="!(selectedRow && selectedRow.length === 1)">
<clr-icon class="margin-top-0" size="16" shape="pencil"></clr-icon>
<span class="margin-left-10">{{'BUTTON.EDIT' | translate}}</span>
</button>
<button clrDropdownItem (click)="deleteWebhook()"
class="btn btn-secondary" [disabled]="!(selectedRow && selectedRow.length >= 1)">
<clr-icon class="margin-top-0" size="16" shape="times"></clr-icon>
<span id="delete-scanner-action"
class="margin-left-10">{{'BUTTON.DELETE' | translate}}</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)="success()" [hidden]="loadingWebhookList || loadingMetadata"></clr-icon>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 datagrid-margin-top">
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-column>{{'WEBHOOK.TYPE' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.STATUS' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.CREATED' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.LAST_TRIGGERED' | translate}}</clr-dg-column>
<clr-dg-row *clrDgItems="let item of lastTriggers">
<clr-dg-cell>{{item.event_type}}</clr-dg-cell>
<clr-dg-cell [ngSwitch]="item.enabled">
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'WEBHOOK.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.NOTIFY_TYPE' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.TARGET' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.ENABLED' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.EVENT_TYPES' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.CREATED' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.DESCRIPTION' | translate}}</clr-dg-column>
<clr-dg-placeholder>
{{'WEBHOOK.NO_WEBHOOK' | translate}}
</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let w of webhookList" [clrDgItem]="w">
<clr-dg-cell>{{w.name}}</clr-dg-cell>
<clr-dg-cell>{{w?.targets[0].type}}</clr-dg-cell>
<clr-dg-cell>{{w?.targets[0].address}}</clr-dg-cell>
<clr-dg-cell [ngSwitch]="w.enabled">
<div *ngSwitchCase="true" class="icon-wrap">
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
<span>{{'WEBHOOK.ENABLED' | translate}}</span>
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
<span>{{'WEBHOOK.ENABLED' | translate}}</span>
</div>
<div *ngSwitchCase="false" class="icon-wrap">
<clr-icon shape="exclamation-triangle" size="20" class="is-warning"></clr-icon>
<span>{{'WEBHOOK.DISABLED' | translate}}</span>
<clr-icon shape="exclamation-triangle" size="20" class="is-warning"></clr-icon>
<span>{{'WEBHOOK.DISABLED' | translate}}</span>
</div>
</clr-dg-cell>
<clr-dg-cell>{{item.creation_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{item.last_trigger_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="lastTriggerCount">1 - {{lastTriggerCount}} {{'WEBHOOK.OF' | translate}} </span> {{lastTriggerCount}} {{'WEBHOOK.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="10"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
<div *ngIf="showCreate">
<p class="create-text pt-1">{{'WEBHOOK.CREATE_WEBHOOK_DESC' | translate}}</p>
<add-webhook-form class="webhook-form-wrap" [projectId]="projectId" [webhook]="webhook" [isModify]="false" (edit)="editWebhook($event)"></add-webhook-form>
</div>
<add-webhook [projectId]="projectId" [webhook]="webhook" (modify)="editWebhook($event)"></add-webhook>
<confirmation-dialog #confirmationDialogComponent (confirmAction)="confirmSwitch($event)"></confirmation-dialog>
</clr-dg-cell>
<clr-dg-cell>
<div class="cell" *ngIf="w?.event_types?.length">
<div class="bar-state">
<span class="label" *ngIf="w?.event_types[0]">{{w?.event_types[0]}}</span>
</div>
<div class="signpost-item" [hidden]="w?.event_types?.length<=1">
<div class="trigger-item">
<clr-signpost>
<button class="btn btn-link font-size-20" clrSignpostTrigger>...</button>
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
<div>
<div *ngFor="let e of w?.event_types" class="bar-state">
<span class="label not-scan">{{e}}</span>
</div>
</div>
</clr-signpost-content>
</clr-signpost>
</div>
</div>
</div>
</clr-dg-cell>
<clr-dg-cell>{{w.creation_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{w.description}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded>
<last-trigger class="w-100" [webhookName]="w.name" *clrIfExpanded [inputLastTriggers]="lastTriggers"></last-trigger>
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="webhookList?.length > 0">1 - {{webhookList?.length}} {{'WEBHOOK.OF' | translate}} </span> {{webhookList?.length}} {{'WEBHOOK.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="10"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
<add-webhook (notify)="success()" [metadata]="metadata" [projectId]="projectId"></add-webhook>
<confirmation-dialog #confirmationDialogComponent (confirmAction)="confirmSwitch($event)"></confirmation-dialog>

View File

@ -39,3 +39,19 @@
justify-content: center;
align-items: center;
}
.cell {
display: flex;
align-items: center;
width: 100%;
height: 100%;
}
.font-size-20 {
font-size: 20px;
}
.action-head-pos {
padding-right: 18px;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
}

View File

@ -11,26 +11,69 @@ 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 { Webhook } from "./webhook";
import { AddWebhookFormComponent } from "./add-webhook-form/add-webhook-form.component";
import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component";
import { AddWebhookComponent } from "./add-webhook/add-webhook.component";
import { ConfirmationDialogComponent } from "../../../lib/components/confirmation-dialog";
describe('WebhookComponent', () => {
let component: WebhookComponent;
let fixture: ComponentFixture<WebhookComponent>;
const mockMessageHandlerService = {
handleError: () => { }
};
const mockedMetadata = {
"event_type": [
"projectQuota",
"pullImage",
"scanningFailed",
"uploadChart",
"deleteChart",
"downloadChart",
"scanningCompleted",
"pushImage",
"deleteImage"
],
"notify_type": [
"http",
"slack"
]
};
const mockedWehook: Webhook = {
id: 1,
project_id: 1,
name: 'test',
description: 'just a test webhook',
targets: [{
address: 'https://test.com',
type: 'http',
attachment: null,
auth_header: null,
skip_cert_verify: true,
}],
event_types: [
'projectQuota'
],
creator: null,
creation_time: null,
update_time: null,
enabled: true,
};
const mockWebhookService = {
listLastTrigger: () => {
return of([]);
return of([]).pipe(delay(0));
},
listWebhook: () => {
return of([
{
targets: [
{ address: "" }
],
enabled: true
}
]);
return of([mockedWehook
]).pipe(delay(0));
},
getWebhookMetadata() {
return of(mockedMetadata).pipe(delay(0));
},
editWebhook() {
return of(true);
}
};
const mockActivatedRoute = {
RouterparamMap: of({ get: (key) => 'value' }),
@ -61,7 +104,12 @@ describe('WebhookComponent', () => {
NoopAnimationsModule,
HttpClientTestingModule
],
declarations: [WebhookComponent],
declarations: [WebhookComponent,
AddWebhookComponent,
AddWebhookFormComponent,
InlineAlertComponent,
ConfirmationDialogComponent
],
providers: [
TranslateService,
{ provide: WebhookService, useValue: mockWebhookService },
@ -72,13 +120,69 @@ describe('WebhookComponent', () => {
.compileComponents();
}));
beforeEach(() => {
beforeEach(async () => {
fixture = TestBed.createComponent(WebhookComponent);
component = fixture.componentInstance;
fixture.detectChanges();
fixture.autoDetectChanges(true);
await fixture.whenStable();
});
it('should create', () => {
it('should create', async () => {
expect(component).toBeTruthy();
});
it('should get webhook list', async () => {
const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row');
expect(rows.length).toEqual(1);
});
it('should open modal', async () => {
component.newWebhook();
fixture.detectChanges();
await fixture.whenStable();
const body: HTMLElement = fixture.nativeElement.querySelector(".modal-body");
expect(body).toBeTruthy();
const title: HTMLElement = fixture.nativeElement.querySelector(".modal-title");
expect(title.innerText).toEqual('WEBHOOK.ADD_WEBHOOK');
});
it('should open edit modal', async () => {
component.webhookList[0].name = 'test';
component.selectedRow[0] = component.webhookList[0];
component.editWebhook();
fixture.detectChanges();
await fixture.whenStable();
const body: HTMLElement = fixture.nativeElement.querySelector(".modal-body");
expect(body).toBeTruthy();
const title: HTMLElement = fixture.nativeElement.querySelector(".modal-title");
expect(title.innerText).toEqual('WEBHOOK.EDIT_WEBHOOK');
const nameInput: HTMLInputElement = fixture.nativeElement.querySelector("#name");
expect(nameInput.value).toEqual('test');
});
it('should disable webhook', async () => {
await fixture.whenStable();
component.selectedRow[0] = component.webhookList[0];
component.webhookList[0].enabled = true;
component.switchWebhookStatus();
fixture.detectChanges();
await fixture.whenStable();
const button: HTMLButtonElement = fixture.nativeElement.querySelector("#dialog-action-disable");
button.dispatchEvent(new Event('click'));
await fixture.whenStable();
const body: HTMLElement = fixture.nativeElement.querySelector(".modal-body");
expect(body).toBeFalsy();
});
it('should enable webhook', async () => {
await fixture.whenStable();
component.webhookList[0].enabled = false;
component.selectedRow[0] = component.webhookList[0];
component.switchWebhookStatus();
fixture.detectChanges();
await fixture.whenStable();
const buttonEnable: HTMLButtonElement = fixture.nativeElement.querySelector("#dialog-action-enable");
buttonEnable.dispatchEvent(new Event('click'));
await fixture.whenStable();
const bodyEnable: HTMLElement = fixture.nativeElement.querySelector(".modal-body");
expect(bodyEnable).toBeFalsy();
});
});

View File

@ -28,13 +28,13 @@ import {
} from "../../shared/shared.const";
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../../shared/confirmation-dialog/confirmation-state-message";
import { ConfirmationDialogComponent } from "../../shared/confirmation-dialog/confirmation-dialog.component";
import { clone } from "../../../lib/utils/utils";
import { forkJoin, Observable } from "rxjs";
@Component({
templateUrl: './webhook.component.html',
styleUrls: ['./webhook.component.scss'],
// changeDetection: ChangeDetectionStrategy.OnPush
styleUrls: ['./webhook.component.scss']
})
export class WebhookComponent implements OnInit {
@ViewChild(AddWebhookComponent, { static: false } )
@ -43,16 +43,16 @@ export class WebhookComponent implements OnInit {
addWebhookFormComponent: AddWebhookFormComponent;
@ViewChild("confirmationDialogComponent", { static: false })
confirmationDialogComponent: ConfirmationDialogComponent;
webhook: Webhook;
endpoint: string = '';
lastTriggers: LastTrigger[] = [];
lastTriggerCount: number = 0;
isEnabled: boolean;
loading: boolean = false;
showCreate: boolean = false;
loadingWebhook: boolean = true;
projectId: number;
projectName: string;
selectedRow: Webhook[] = [];
webhookList: Webhook[] = [];
metadata: any;
loadingMetadata: boolean = false;
loadingWebhookList: boolean = false;
loadingTriggers: boolean = false;
constructor(
private route: ActivatedRoute,
private translate: TranslateService,
@ -66,19 +66,34 @@ export class WebhookComponent implements OnInit {
let project = <Project>(resolverData["projectResolver"]);
this.projectName = project.name;
}
this.getData(this.projectId);
this.getData();
}
getData(projectId: number) {
this.getLastTriggers(projectId);
this.getWebhook(projectId);
getData() {
this.getMetadata();
this.getLastTriggers();
this.getWebhooks();
this.selectedRow = [];
}
getMetadata() {
this.loadingMetadata = true;
this.webhookService.getWebhookMetadata(this.projectId)
.pipe(finalize(() => (this.loadingMetadata = false)))
.subscribe(
response => {
this.metadata = response;
},
error => {
this.messageHandlerService.handleError(error);
}
);
}
getLastTriggers(projectId: number) {
this.loading = true;
getLastTriggers() {
this.loadingTriggers = true;
this.webhookService
.listLastTrigger(projectId)
.pipe(finalize(() => (this.loading = false)))
.listLastTrigger(this.projectId)
.pipe(finalize(() => (this.loadingTriggers = false)))
.subscribe(
response => {
this.lastTriggers = response;
@ -90,20 +105,14 @@ export class WebhookComponent implements OnInit {
);
}
getWebhook(projectId: number) {
getWebhooks() {
this.loadingWebhookList = true;
this.webhookService
.listWebhook(projectId)
.pipe(finalize(() => (this.loadingWebhook = false)))
.listWebhook(this.projectId)
.pipe(finalize(() => (this.loadingWebhookList = false)))
.subscribe(
response => {
if (response.length) {
this.webhook = response[0];
this.endpoint = this.webhook.targets[0].address;
this.isEnabled = this.webhook.enabled;
this.showCreate = false;
} else {
this.showCreate = true;
}
this.webhookList = response;
},
error => {
this.messageHandlerService.handleError(error);
@ -111,46 +120,103 @@ export class WebhookComponent implements OnInit {
);
}
switchWebhookStatus(enabled = false) {
switchWebhookStatus() {
let content = '';
this.translate.get(
enabled
!this.selectedRow[0].enabled
? 'WEBHOOK.ENABLED_WEBHOOK_SUMMARY'
: 'WEBHOOK.DISABLED_WEBHOOK_SUMMARY'
).subscribe((res) => content = res + this.projectName);
let message = new ConfirmationMessage(
enabled ? 'WEBHOOK.ENABLED_WEBHOOK_TITLE' : 'WEBHOOK.DISABLED_WEBHOOK_TITLE',
content,
'',
{},
ConfirmationTargets.WEBHOOK,
enabled ? ConfirmationButtons.ENABLE_CANCEL : ConfirmationButtons.DISABLE_CANCEL
);
this.confirmationDialogComponent.open(message);
, {name: this.selectedRow[0].name}).subscribe((res) => {
content = res;
let message = new ConfirmationMessage(
!this.selectedRow[0].enabled ? 'WEBHOOK.ENABLED_WEBHOOK_TITLE' : 'WEBHOOK.DISABLED_WEBHOOK_TITLE',
content,
'',
{},
ConfirmationTargets.WEBHOOK,
!this.selectedRow[0].enabled ? ConfirmationButtons.ENABLE_CANCEL : ConfirmationButtons.DISABLE_CANCEL
);
this.confirmationDialogComponent.open(message);
});
}
confirmSwitch(message: ConfirmationAcknowledgement) {
confirmSwitch(message) {
if (message &&
message.source === ConfirmationTargets.WEBHOOK &&
message.state === ConfirmationState.CONFIRMED) {
this.webhookService
.editWebhook(this.projectId, this.webhook.id, Object.assign({}, this.webhook, { enabled: !this.isEnabled }))
.subscribe(
if (JSON.stringify(message.data) === '{}') {
this.webhookService
.editWebhook(this.projectId, this.selectedRow[0].id,
Object.assign({}, this.selectedRow[0], { enabled: !this.selectedRow[0].enabled }))
.subscribe(
response => {
this.getData();
},
error => {
this.messageHandlerService.handleError(error);
}
);
} else {
const observableLists: Observable<any>[] = [];
message.data.forEach(item => {
observableLists.push(this.webhookService.deleteWebhook(this.projectId, item.id));
});
forkJoin(...observableLists).subscribe(
response => {
this.getData(this.projectId);
this.getData();
},
error => {
this.messageHandlerService.handleError(error);
}
);
}
}
}
editWebhook(isModify: boolean): void {
this.getData(this.projectId);
editWebhook() {
if (this.metadata) {
this.addWebhookComponent.isOpen = true;
this.addWebhookComponent.isEdit = true;
this.addWebhookComponent.addWebhookFormComponent.isModify = true;
this.addWebhookComponent.addWebhookFormComponent.webhook = clone(this.selectedRow[0]);
this.addWebhookComponent.addWebhookFormComponent.webhook.event_types = clone(this.selectedRow[0].event_types);
}
}
openAddWebhookModal(): void {
this.addWebhookComponent.openAddWebhookModal();
}
newWebhook() {
if (this.metadata) {
this.addWebhookComponent.isOpen = true;
this.addWebhookComponent.isEdit = false;
this.addWebhookComponent.addWebhookFormComponent.isModify = false;
this.addWebhookComponent.addWebhookFormComponent.currentForm.reset({notifyType: this.metadata.notify_type[0]});
this.addWebhookComponent.addWebhookFormComponent.webhook = new Webhook();
this.addWebhookComponent.addWebhookFormComponent.webhook.event_types = clone(this.metadata.event_type);
}
}
success() {
this.getData();
}
deleteWebhook() {
const names: string[] = [];
this.selectedRow.forEach(item => {
names.push(item.name);
});
let content = '';
this.translate.get(
'WEBHOOK.DELETE_WEBHOOK_SUMMARY'
, {names: names.join(',')}).subscribe((res) => content = res);
const msg: ConfirmationMessage = new ConfirmationMessage(
"SCANNER.CONFIRM_DELETION",
content,
names.join(','),
this.selectedRow,
ConfirmationTargets.WEBHOOK,
ConfirmationButtons.DELETE_CANCEL
);
this.confirmationDialogComponent.open(msg);
}
}

View File

@ -11,8 +11,8 @@
// 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 { throwError as observableThrowError, Observable } from "rxjs";
import { map, catchError } from "rxjs/operators";
import { throwError as observableThrowError, Observable, of } from "rxjs";
import { map, catchError, delay } from "rxjs/operators";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Webhook, LastTrigger } from "./webhook";
@ -42,6 +42,12 @@ export class WebhookService {
.pipe(catchError(error => observableThrowError(error)));
}
public deleteWebhook(projectId: number, policyId: number): Observable<any> {
return this.http
.delete(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies/${policyId}`)
.pipe(catchError(error => observableThrowError(error)));
}
public createWebhook(projectId: number, data: any): Observable<any> {
return this.http
.post(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies`, data)
@ -54,4 +60,10 @@ export class WebhookService {
.post(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies/test`, param)
.pipe(catchError(error => observableThrowError(error)));
}
public getWebhookMetadata(projectId: number): Observable<any> {
return this.http
.get(`${CURRENT_BASE_HREF}/projects/${projectId}/webhook/events`)
.pipe(catchError(error => observableThrowError(error)));
}
}

View File

@ -1,16 +1,20 @@
import { WebhookEventTypes } from '../../shared/shared.const';
export class Webhook {
id: number;
name: string;
project_id: number;
description: string;
targets: Target[];
event_types: WebhookEventTypes[];
event_types: string[];
creator: string;
creation_time: Date;
update_time: Date;
enabled: boolean;
constructor () {
this.targets = [];
this.targets.push(new Target());
this.event_types = [];
this.enabled = true;
}
}
export class Target {
@ -28,6 +32,7 @@ export class Target {
}
export class LastTrigger {
policy_name: string;
enabled: boolean;
event_type: string;
creation_time: Date;

View File

@ -99,15 +99,3 @@ export enum ResourceType {
CHART_VERSION = 2,
REPOSITORY_TAG = 3,
}
export enum WebhookEventTypes {
DOWNLOAD_CHART = "downloadChart",
DELETE_CHART = "deleteChart",
UPLOAD_CHART = "uploadChart",
DELETE_IMAGE = "deleteImage",
PULL_IMAGE = "pullImage",
PUSH_IMAGE = "pushImage",
SCANNING_FAILED = "scanningFailed",
SCANNING_COMPLETED = "scanningCompleted",
PROJECT_QUOTA = "projectQuota",
}

View File

@ -363,7 +363,8 @@
"OF": "of",
"ITEMS": "items",
"LAST_TRIGGERED": "Last Triggered",
"EDIT_WEBHOOK": "Webhook Endpoint",
"EDIT_WEBHOOK": "Edit Webhook",
"ADD_WEBHOOK": "Add Webhook",
"CREATE_WEBHOOK": "Getting started with webhooks",
"EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications",
"CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.",
@ -377,10 +378,28 @@
"SAVE_BUTTON": "SAVE",
"TEST_ENDPOINT_SUCCESS": "Connection tested successfully.",
"TEST_ENDPOINT_FAILURE": "Failed to ping endpoint.",
"ENABLED_WEBHOOK_TITLE": "Enable Project Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhooks for project ",
"DISABLED_WEBHOOK_TITLE": "Disable Project Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhooks for project "
"ENABLED_WEBHOOK_TITLE": "Enable Webhook",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhook {{name}}?",
"DISABLED_WEBHOOK_TITLE": "Disable Webhook",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhook {{name}}?",
"DELETE_WEBHOOK_TITLE": "Delete Webhook(s)",
"DELETE_WEBHOOK_SUMMARY": "Do you want to delete webhook(s) {{names}}?",
"WEBHOOKS": "Webhooks",
"NEW_WEBHOOK": "New Webhook",
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",
"LAST_TRIGGER": "Last Trigger",
"WEBHOOK_NAME": "Webhook Name",
"NO_TRIGGER": "No Trigger",
"NAME_REQUIRED": "Name is required",
"NOTIFY_TYPE": "Notify Type",
"EVENT_TYPE": "Event Type",
"EVENT_TYPE_REQUIRED": "Require at least one event type"
},
"GROUP": {
"GROUP": "Group",

View File

@ -364,7 +364,8 @@
"OF": "of",
"ITEMS": "items",
"LAST_TRIGGERED": "Last Triggered",
"EDIT_WEBHOOK": "Webhook Endpoint",
"EDIT_WEBHOOK": "Edit Webhook",
"ADD_WEBHOOK": "Add Webhook",
"CREATE_WEBHOOK": "Getting started with webhooks",
"EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications",
"CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.",
@ -378,10 +379,28 @@
"SAVE_BUTTON": "SAVE",
"TEST_ENDPOINT_SUCCESS": "Connection tested successfully.",
"TEST_ENDPOINT_FAILURE": "Failed to ping endpoint.",
"ENABLED_WEBHOOK_TITLE": "Enable Project Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhooks for project ",
"DISABLED_WEBHOOK_TITLE": "Disable Project Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhooks for project "
"ENABLED_WEBHOOK_TITLE": "Enable Webhook",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhook {{name}}?",
"DISABLED_WEBHOOK_TITLE": "Disable Webhook",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhook {{name}}?",
"DELETE_WEBHOOK_TITLE": "Delete Webhook(s)",
"DELETE_WEBHOOK_SUMMARY": "Do you want to delete webhook(s) {{names}}?",
"WEBHOOKS": "Webhooks",
"NEW_WEBHOOK": "New Webhook",
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",
"LAST_TRIGGER": "Last Trigger",
"WEBHOOK_NAME": "Webhook Name",
"NO_TRIGGER": "No Trigger",
"NAME_REQUIRED": "Name is required",
"NOTIFY_TYPE": "Notify Type",
"EVENT_TYPE": "Event Type",
"EVENT_TYPE_REQUIRED": "Require at least one event type"
},
"GROUP": {
"GROUP": "Group",

View File

@ -355,7 +355,8 @@
"OF": "of",
"ITEMS": "items",
"LAST_TRIGGERED": "Last Triggered",
"EDIT_WEBHOOK": "Webhook Endpoint",
"EDIT_WEBHOOK": "Edit Webhook",
"ADD_WEBHOOK": "Add Webhook",
"CREATE_WEBHOOK": "Getting started with webhooks",
"EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications",
"CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.",
@ -369,10 +370,28 @@
"SAVE_BUTTON": "SAVE",
"TEST_ENDPOINT_SUCCESS": "Connection tested successfully.",
"TEST_ENDPOINT_FAILURE": "Failed to ping endpoint.",
"ENABLED_WEBHOOK_TITLE": "Enable Project Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhooks for project ",
"DISABLED_WEBHOOK_TITLE": "Disable Project Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhooks for project "
"ENABLED_WEBHOOK_TITLE": "Enable Webhook",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhook {{name}}?",
"DISABLED_WEBHOOK_TITLE": "Disable Webhook",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhook {{name}}?",
"DELETE_WEBHOOK_TITLE": "Delete Webhook(s)",
"DELETE_WEBHOOK_SUMMARY": "Do you want to delete webhook(s) {{names}}?",
"WEBHOOKS": "Webhooks",
"NEW_WEBHOOK": "New Webhook",
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",
"LAST_TRIGGER": "Last Trigger",
"WEBHOOK_NAME": "Webhook Name",
"NO_TRIGGER": "No Trigger",
"NAME_REQUIRED": "Name is required",
"NOTIFY_TYPE": "Notify Type",
"EVENT_TYPE": "Event Type",
"EVENT_TYPE_REQUIRED": "Require at least one event type"
},
"GROUP": {
"Group": "Group",

View File

@ -393,7 +393,8 @@
"OF": "of",
"ITEMS": "items",
"LAST_TRIGGERED": "Last Triggered",
"EDIT_WEBHOOK": "Webhook Endpoint",
"EDIT_WEBHOOK": "Edit Webhook",
"ADD_WEBHOOK": "Add Webhook",
"CREATE_WEBHOOK": "Getting started with webhooks",
"EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications",
"CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.",
@ -407,10 +408,28 @@
"SAVE_BUTTON": "SAVE",
"TEST_ENDPOINT_SUCCESS": "Connection tested successfully.",
"TEST_ENDPOINT_FAILURE": "Failed to ping endpoint.",
"ENABLED_WEBHOOK_TITLE": "Enable Project Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhooks for project ",
"DISABLED_WEBHOOK_TITLE": "Disable Project Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhooks for project "
"ENABLED_WEBHOOK_TITLE": "Enable Webhook",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhook {{name}}?",
"DISABLED_WEBHOOK_TITLE": "Disable Webhook",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhook {{name}}?",
"DELETE_WEBHOOK_TITLE": "Delete Webhook(s)",
"DELETE_WEBHOOK_SUMMARY": "Do you want to delete webhook(s) {{names}}?",
"WEBHOOKS": "Webhooks",
"NEW_WEBHOOK": "New Webhook",
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",
"LAST_TRIGGER": "Last Trigger",
"WEBHOOK_NAME": "Webhook Name",
"NO_TRIGGER": "No Trigger",
"NAME_REQUIRED": "Name is required",
"NOTIFY_TYPE": "Notify Type",
"EVENT_TYPE": "Event Type",
"EVENT_TYPE_REQUIRED": "Require at least one event type"
},
"AUDIT_LOG": {
"USERNAME": "Nome do usuário",

View File

@ -363,7 +363,8 @@
"OF": "of",
"ITEMS": "adetler",
"LAST_TRIGGERED": "Son Tetiklenen",
"EDIT_WEBHOOK": "Ağ Kancası Uç Noktası",
"EDIT_WEBHOOK": "Edit Webhook",
"ADD_WEBHOOK": "Add Webhook",
"CREATE_WEBHOOK": "Ağ kancaları ile başladı",
"EDIT_WEBHOOK_DESC": "Ağ kancası bildirimleri almak için bitiş noktasını belirtin",
"CREATE_WEBHOOK_DESC": "Ağ kancalarına başlamak için, web kanca sunucusuna erişmek için bir uç nokta ve kimlik bilgisi sağlayın.",
@ -377,10 +378,28 @@
"SAVE_BUTTON": "KAYDET",
"TEST_ENDPOINT_SUCCESS": "Connection tested successfully.",
"TEST_ENDPOINT_FAILURE": "Failed to ping endpoint.",
"ENABLED_WEBHOOK_TITLE": "Proje Ağ Kancalarını Etkinleştir",
"ENABLED_WEBHOOK_SUMMARY": "Proje için ağ kancalarını etkinleştirmek istiyor musunuz?",
"DISABLED_WEBHOOK_TITLE": "Proje Ağ kancalarını Devre Dışı Bırak",
"DISABLED_WEBHOOK_SUMMARY": "Proje için ağ kancalarını devre dışı bırakmak istiyor musunuz?"
"ENABLED_WEBHOOK_TITLE": "Enable Webhook",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhook {{name}}?",
"DISABLED_WEBHOOK_TITLE": "Disable Webhook",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhook {{name}}?",
"DELETE_WEBHOOK_TITLE": "Delete Webhook(s)",
"DELETE_WEBHOOK_SUMMARY": "Do you want to delete webhook(s) {{names}}?",
"WEBHOOKS": "Webhooks",
"NEW_WEBHOOK": "New Webhook",
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",
"LAST_TRIGGER": "Last Trigger",
"WEBHOOK_NAME": "Webhook Name",
"NO_TRIGGER": "No Trigger",
"NAME_REQUIRED": "Name is required",
"NOTIFY_TYPE": "Notify Type",
"EVENT_TYPE": "Event Type",
"EVENT_TYPE_REQUIRED": "Require at least one event type"
},
"GROUP": {
"GROUP": "Grup",

View File

@ -361,8 +361,9 @@
"DISABLED": "停用",
"OF": "共计",
"ITEMS": "条记录",
"LAST_TRIGGERED": "最近触发事件",
"EDIT_WEBHOOK": "Webhook 目标",
"LAST_TRIGGERED": "最近触发时间",
"EDIT_WEBHOOK": "编辑 Webhook",
"ADD_WEBHOOK": "新建 Webhook",
"CREATE_WEBHOOK": "创建 Webhooks",
"EDIT_WEBHOOK_DESC": "指定接收 Webhook 通知的目标",
"CREATE_WEBHOOK_DESC": "为了启用 webhook, 请提供 Endpoint 和凭据以访问 Webhook 服务器。",
@ -376,10 +377,28 @@
"SAVE_BUTTON": "保存",
"TEST_ENDPOINT_SUCCESS": "测试连接成功。",
"TEST_ENDPOINT_FAILURE": "测试连接失败。",
"ENABLED_WEBHOOK_TITLE": "启用项目的 Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "你希望开启项目的 Webhooks 吗?",
"DISABLED_WEBHOOK_TITLE": "停用项目的 Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "你希望停用项目的 Webhooks 吗?"
"ENABLED_WEBHOOK_TITLE": "启用 Webhook",
"ENABLED_WEBHOOK_SUMMARY": "确认启用 webhook {{name}}",
"DISABLED_WEBHOOK_TITLE": "停用 Webhook",
"DISABLED_WEBHOOK_SUMMARY": "确认停用 webhook {{name}}",
"DELETE_WEBHOOK_TITLE": "删除 Webhook(s)",
"DELETE_WEBHOOK_SUMMARY": "确认删除 webhook(s) {{names}}?",
"WEBHOOKS": "Webhooks",
"NEW_WEBHOOK": "新建 Webhook",
"ENABLE": "启用",
"DISABLE": "禁用",
"NAME": "名称",
"TARGET": "目标地址",
"EVENT_TYPES": "事件类型",
"DESCRIPTION": "简介",
"NO_WEBHOOK": "暂无 Webhook 记录",
"LAST_TRIGGER": "最新触发",
"WEBHOOK_NAME": "Webhook 名称",
"NO_TRIGGER": "暂无触发记录",
"NAME_REQUIRED": "名称为必填项",
"NOTIFY_TYPE": "通知类型",
"EVENT_TYPE": "事件类型",
"EVENT_TYPE_REQUIRED": "请至少选择一种事件类型"
},
"GROUP": {
"GROUP": "组",