diff --git a/src/portal/src/app/project/project.module.ts b/src/portal/src/app/project/project.module.ts index 9c230cb42..c4f19e1f4 100644 --- a/src/portal/src/app/project/project.module.ts +++ b/src/portal/src/app/project/project.module.ts @@ -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: [ diff --git a/src/portal/src/app/project/scanner/scanner.component.html b/src/portal/src/app/project/scanner/scanner.component.html index d25396c5b..43351736e 100644 --- a/src/portal/src/app/project/scanner/scanner.component.html +++ b/src/portal/src/app/project/scanner/scanner.component.html @@ -12,7 +12,7 @@
- +
diff --git a/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.html b/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.html index e0c903705..f4428c6b2 100644 --- a/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.html +++ b/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.html @@ -2,13 +2,57 @@
+ +
+ +
+
+ + +
+ + {{'WEBHOOK.NAME_REQUIRED' | translate}} + +
+
+ +
+ +
+ +
+
+ + + + + + +
+ +
+
+ + +
+
+ + {{'WEBHOOK.EVENT_TYPE_REQUIRED' | translate}} +
+
+
+
- +
@@ -17,16 +61,18 @@
-
+
- +
+ +
+ (ngModelChange)="setCertValue($event)" [ngModel]="!webhook?.targets[0]?.skip_cert_verify"/>
- - + + +
+ -
\ No newline at end of file diff --git a/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.scss b/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.scss index 407273a5d..bafd18d06 100644 --- a/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.scss +++ b/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.scss @@ -7,4 +7,7 @@ .bottom-btn { text-align: right; margin-right: 3.4rem; +} +.width-238 { + width: 238px; } \ No newline at end of file diff --git a/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.spec.ts b/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.spec.ts index 2f1164a9b..6070c40ca 100644 --- a/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.spec.ts +++ b/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.spec.ts @@ -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); }); }); diff --git a/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.ts b/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.ts index e31467b8b..3af1f4971 100644 --- a/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.ts +++ b/src/portal/src/app/project/webhook/add-webhook-form/add-webhook-form.component.ts @@ -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(); @Output() close = new EventEmitter(); @ViewChild("webhookForm", { static: true }) currentForm: NgForm; @ViewChild(InlineAlertComponent, { static: false }) inlineAlert: InlineAlertComponent; - + @Input() + metadata: any; + @Output() notify = new EventEmitter(); 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; + } } diff --git a/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.html b/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.html index 589376278..c43a86d8e 100644 --- a/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.html +++ b/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.html @@ -1,13 +1,12 @@ - + + \ No newline at end of file diff --git a/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.spec.ts b/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.spec.ts index 342375d44..628f24cb6 100644 --- a/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.spec.ts +++ b/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.spec.ts @@ -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'); + }); }); diff --git a/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.ts b/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.ts index 4c3168036..638d26da2 100644 --- a/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.ts +++ b/src/portal/src/app/project/webhook/add-webhook/add-webhook.component.ts @@ -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(); + webhook: Webhook; + @Input() + metadata: any; @ViewChild(AddWebhookFormComponent, { static: false }) addWebhookFormComponent: AddWebhookFormComponent; - + @Output() notify = new EventEmitter(); 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; } - } diff --git a/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.html b/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.html new file mode 100644 index 000000000..b748ab363 --- /dev/null +++ b/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.html @@ -0,0 +1,29 @@ +

{{'WEBHOOK.LAST_TRIGGER' | translate}}

+ + {{'WEBHOOK.TYPE' | translate}} + {{'WEBHOOK.STATUS' | translate}} + {{'WEBHOOK.LAST_TRIGGERED' | translate}} + + {{item.event_type}} + +
+ + {{'WEBHOOK.ENABLED' | translate}} +
+
+ + {{'WEBHOOK.DISABLED' | translate}} +
+
+ {{item.last_trigger_time | date: 'short'}} +
+ + {{'WEBHOOK.NO_TRIGGER' | translate}} + + + 1 - {{lastTriggers.length}} {{'WEBHOOK.OF' | translate}} {{lastTriggers.length}} {{'WEBHOOK.ITEMS' | translate}} + + +
+ + diff --git a/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.scss b/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.spec.ts b/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.spec.ts new file mode 100644 index 000000000..f57ee6c94 --- /dev/null +++ b/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.spec.ts @@ -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; + 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); + }); +}); diff --git a/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.ts b/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.ts new file mode 100644 index 000000000..33b0dab96 --- /dev/null +++ b/src/portal/src/app/project/webhook/last-trigger/last-trigger.component.ts @@ -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 { + } +} diff --git a/src/portal/src/app/project/webhook/webhook.component.html b/src/portal/src/app/project/webhook/webhook.component.html index 6becf8adc..77cbeb378 100644 --- a/src/portal/src/app/project/webhook/webhook.component.html +++ b/src/portal/src/app/project/webhook/webhook.component.html @@ -1,54 +1,109 @@ -
- -
-
-
-
-
-
-
- Webhook endpoint: {{endpoint}} - -
-
- - -
+
+

{{'WEBHOOK.WEBHOOKS' | translate}}

+ + +
+
+ + + {{'MEMBER.ACTION' | translate}} + + + + + + + +
+
+
+ + + +
+
-
-
-
- - {{'WEBHOOK.TYPE' | translate}} - {{'WEBHOOK.STATUS' | translate}} - {{'WEBHOOK.CREATED' | translate}} - {{'WEBHOOK.LAST_TRIGGERED' | translate}} - - {{item.event_type}} - + + {{'WEBHOOK.NAME' | translate}} + {{'WEBHOOK.NOTIFY_TYPE' | translate}} + {{'WEBHOOK.TARGET' | translate}} + {{'WEBHOOK.ENABLED' | translate}} + {{'WEBHOOK.EVENT_TYPES' | translate}} + {{'WEBHOOK.CREATED' | translate}} + {{'WEBHOOK.DESCRIPTION' | translate}} + + {{'WEBHOOK.NO_WEBHOOK' | translate}} + + + {{w.name}} + {{w?.targets[0].type}} + {{w?.targets[0].address}} +
- - {{'WEBHOOK.ENABLED' | translate}} + + {{'WEBHOOK.ENABLED' | translate}}
- - {{'WEBHOOK.DISABLED' | translate}} + + {{'WEBHOOK.DISABLED' | translate}}
-
- {{item.creation_time | date: 'short'}} - {{item.last_trigger_time | date: 'short'}} -
- - 1 - {{lastTriggerCount}} {{'WEBHOOK.OF' | translate}} {{lastTriggerCount}} {{'WEBHOOK.ITEMS' | translate}} - - -
-
-
-
-

{{'WEBHOOK.CREATE_WEBHOOK_DESC' | translate}}

- -
- - + + +
+
+ {{w?.event_types[0]}} +
+
+
+ + + +
+
+ {{e}} +
+
+
+
+
+
+
+
+ {{w.creation_time | date: 'short'}} + {{w.description}} + + + + + + 1 - {{webhookList?.length}} {{'WEBHOOK.OF' | translate}} {{webhookList?.length}} {{'WEBHOOK.ITEMS' | translate}} + + +
+ + + + diff --git a/src/portal/src/app/project/webhook/webhook.component.scss b/src/portal/src/app/project/webhook/webhook.component.scss index 14bc6e654..c1dc83ec2 100644 --- a/src/portal/src/app/project/webhook/webhook.component.scss +++ b/src/portal/src/app/project/webhook/webhook.component.scss @@ -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; +} \ No newline at end of file diff --git a/src/portal/src/app/project/webhook/webhook.component.spec.ts b/src/portal/src/app/project/webhook/webhook.component.spec.ts index 4e794e6cb..0da4d8c4e 100644 --- a/src/portal/src/app/project/webhook/webhook.component.spec.ts +++ b/src/portal/src/app/project/webhook/webhook.component.spec.ts @@ -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; 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(); + }); }); + + + diff --git a/src/portal/src/app/project/webhook/webhook.component.ts b/src/portal/src/app/project/webhook/webhook.component.ts index 2d4064aef..3fb067a9d 100644 --- a/src/portal/src/app/project/webhook/webhook.component.ts +++ b/src/portal/src/app/project/webhook/webhook.component.ts @@ -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 = (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[] = []; + 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); + } } diff --git a/src/portal/src/app/project/webhook/webhook.service.ts b/src/portal/src/app/project/webhook/webhook.service.ts index e7f7a99ee..3a761a3a0 100644 --- a/src/portal/src/app/project/webhook/webhook.service.ts +++ b/src/portal/src/app/project/webhook/webhook.service.ts @@ -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 { + return this.http + .delete(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies/${policyId}`) + .pipe(catchError(error => observableThrowError(error))); + } + public createWebhook(projectId: number, data: any): Observable { 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 { + return this.http + .get(`${CURRENT_BASE_HREF}/projects/${projectId}/webhook/events`) + .pipe(catchError(error => observableThrowError(error))); + } } diff --git a/src/portal/src/app/project/webhook/webhook.ts b/src/portal/src/app/project/webhook/webhook.ts index 4d11a8c1c..a9ea9268b 100644 --- a/src/portal/src/app/project/webhook/webhook.ts +++ b/src/portal/src/app/project/webhook/webhook.ts @@ -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; diff --git a/src/portal/src/app/shared/shared.const.ts b/src/portal/src/app/shared/shared.const.ts index 3f9d4d979..b969ec816 100644 --- a/src/portal/src/app/shared/shared.const.ts +++ b/src/portal/src/app/shared/shared.const.ts @@ -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", -} diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 49e243b3e..88ced5e01 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 0a6aae648..2edbb9cd9 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index d6f944db3..f7335cbd6 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 921260c04..0eaec87d1 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 2851e02ce..fd4bd741c 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -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", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 95f46d095..7d4a2a1a1 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -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": "组",