diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/WebhookDomainObject.cs b/src/Squidex.Domain.Apps.Write/Webhooks/WebhookDomainObject.cs
index 139643a6c..c3484699d 100644
--- a/src/Squidex.Domain.Apps.Write/Webhooks/WebhookDomainObject.cs
+++ b/src/Squidex.Domain.Apps.Write/Webhooks/WebhookDomainObject.cs
@@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Write.Webhooks
private void VerifyCreatedAndNotDeleted()
{
- if (isDeleted || isCreated)
+ if (isDeleted || !isCreated)
{
throw new DomainException("Webhook has already been deleted or not created yet.");
}
diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs b/src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs
new file mode 100644
index 000000000..7b04c9f0e
--- /dev/null
+++ b/src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs
@@ -0,0 +1,32 @@
+// ==========================================================================
+// WebhookCreatedDto.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace Squidex.Controllers.Api.Webhooks.Models
+{
+ public class WebhookCreatedDto
+ {
+ ///
+ /// The id of the webhook.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// The shared secret that is used to calculate the signature.
+ ///
+ [Required]
+ public string SharedSecret { get; set; }
+
+ ///
+ /// The version of the schema.
+ ///
+ public long Version { get; set; }
+ }
+}
diff --git a/src/Squidex/app/features/webhooks/pages/webhook.component.html b/src/Squidex/app/features/webhooks/pages/webhook.component.html
new file mode 100644
index 000000000..a5005e98b
--- /dev/null
+++ b/src/Squidex/app/features/webhooks/pages/webhook.component.html
@@ -0,0 +1,143 @@
+
+
+
+
+
+
0">
+
+
+ 0">
+ {{webhook.totalSucceeded}}
+
+
+
+ 0">
+ {{webhook.totalFailed}}
+
+
+
+ 0">
+ {{webhook.totalTimedout}}
+
+
+
+
+ {{webhook.averageRequestTimeMs}} ms
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Squidex/app/features/webhooks/pages/webhook.component.scss b/src/Squidex/app/features/webhooks/pages/webhook.component.scss
new file mode 100644
index 000000000..61873b1a2
--- /dev/null
+++ b/src/Squidex/app/features/webhooks/pages/webhook.component.scss
@@ -0,0 +1,13 @@
+@import '_vars';
+@import '_mixins';
+
+.schemas-control {
+ width: 18rem;
+}
+
+.webhook-section {
+ border-top: 1px solid $color-border;
+ margin-left: -1.25rem;
+ margin-right: -1.25rem;
+ padding: 1rem 1.25rem 0;
+}
\ No newline at end of file
diff --git a/src/Squidex/app/features/webhooks/pages/webhook.component.ts b/src/Squidex/app/features/webhooks/pages/webhook.component.ts
new file mode 100644
index 000000000..01b4d3631
--- /dev/null
+++ b/src/Squidex/app/features/webhooks/pages/webhook.component.ts
@@ -0,0 +1,129 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Sebastian Stehle. All rights reserved
+ */
+
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { FormBuilder, Validators } from '@angular/forms';
+
+import {
+ ImmutableArray,
+ SchemaDto,
+ UpdateWebhookDto,
+ WebhookDto
+} from 'shared';
+
+interface WebhookSchemaForm {
+ schema: SchemaDto;
+ sendCreate: boolean;
+ sendUpdate: boolean;
+ sendDelete: boolean;
+ sendPublish: boolean;
+ sendUnpublish: boolean;
+}
+
+@Component({
+ selector: 'sqx-webhook',
+ styleUrls: ['./webhook.component.scss'],
+ templateUrl: './webhook.component.html'
+})
+export class WebhookComponent implements OnInit {
+ @Output()
+ public deleting = new EventEmitter();
+
+ @Output()
+ public updating = new EventEmitter();
+
+ @Input()
+ public allSchemas: SchemaDto[];
+
+ @Input()
+ public webhook: WebhookDto;
+
+ public schemas: ImmutableArray;
+
+ public schemasToAdd: ImmutableArray;
+
+ public webhookFormSubmitted = false;
+ public webhookForm =
+ this.formBuilder.group({
+ url: ['',
+ [
+ Validators.required
+ ]]
+ });
+
+ public addSchemaForm =
+ this.formBuilder.group({
+ schema: [null]
+ });
+
+ public get hasUrl() {
+ return this.webhookForm.controls['url'].value && this.webhookForm.controls['url'].value.length > 0;
+ }
+
+ public get hasSchema() {
+ return this.addSchemaForm.controls['schema'].value;
+ }
+
+ constructor(
+ private readonly formBuilder: FormBuilder
+ ) {
+ }
+
+ public ngOnInit() {
+ this.webhookForm.controls['url'].setValue(this.webhook.url);
+
+ this.schemasToAdd =
+ ImmutableArray.of(
+ this.allSchemas.filter(schema =>
+ !this.webhook.schemas.find(w => w.schemaId === schema.id)))
+ .sortByStringAsc(x => x.name);
+
+ this.schemas =
+ ImmutableArray.of(
+ this.webhook.schemas.map(webhookSchema => {
+ const schema = this.allSchemas.find(s => s.id === webhookSchema.schemaId);
+
+ if (schema) {
+ return {
+ schema: schema,
+ sendCreate: webhookSchema.sendCreate,
+ sendUpdate: webhookSchema.sendUpdate,
+ sendDelete: webhookSchema.sendDelete,
+ sendPublish: webhookSchema.sendPublish,
+ sendUnpublish: webhookSchema.sendUnpublish
+ };
+ } else {
+ return null;
+ }
+ }).filter(w => !!w)).sortByStringAsc(x => x.schema.name);
+
+ this.addSchemaForm.controls['schema'].setValue(this.schemasToAdd.find(x => true));
+ }
+
+ public removeSchema(webhookSchema: WebhookSchemaForm) {
+ this.schemasToAdd = this.schemasToAdd.push(webhookSchema.schema).sortByStringAsc(x => x.name);
+ this.schemas = this.schemas.remove(webhookSchema);
+
+ this.addSchemaForm.controls['schema'].setValue(this.schemasToAdd.find(x => true));
+ }
+
+ public addSchema() {
+ const schema: SchemaDto = this.addSchemaForm.controls['schema'].value;
+
+ this.schemasToAdd = this.schemasToAdd.remove(schema).sortByStringAsc(x => x.name);
+ this.schemas = this.schemas.push({
+ schema: schema,
+ sendCreate: false,
+ sendUpdate: false,
+ sendDelete: false,
+ sendPublish: false,
+ sendUnpublish: false
+ }).sortByStringAsc(x => x.schema.name);
+
+ this.addSchemaForm.controls['schema'].setValue(this.schemasToAdd.find(x => true));
+ }
+}