mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-23 08:01:36 +01:00
Merge pull request #11027 from AllForNothing/webhook
Improve Webhook UI
This commit is contained in:
commit
aa73f16a20
@ -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: [
|
||||
|
@ -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">
|
||||
|
@ -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>
|
@ -7,4 +7,7 @@
|
||||
.bottom-btn {
|
||||
text-align: right;
|
||||
margin-right: 3.4rem;
|
||||
}
|
||||
.width-238 {
|
||||
width: 238px;
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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 {
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "组",
|
||||
|
Loading…
Reference in New Issue
Block a user