diff --git a/app/Actions/ActivityType.php b/app/Actions/ActivityType.php index 60b1630e0..8b5213a8b 100644 --- a/app/Actions/ActivityType.php +++ b/app/Actions/ActivityType.php @@ -53,4 +53,16 @@ class ActivityType const MFA_SETUP_METHOD = 'mfa_setup_method'; const MFA_REMOVE_METHOD = 'mfa_remove_method'; + + const WEBHOOK_CREATE = 'webhook_create'; + const WEBHOOK_UPDATE = 'webhook_update'; + const WEBHOOK_DELETE = 'webhook_delete'; + + /** + * Get all the possible values. + */ + public static function all(): array + { + return (new \ReflectionClass(static::class))->getConstants(); + } } diff --git a/app/Actions/Webhook.php b/app/Actions/Webhook.php index 6939b54d6..2d11584e6 100644 --- a/app/Actions/Webhook.php +++ b/app/Actions/Webhook.php @@ -2,10 +2,24 @@ namespace BookStack\Actions; +use BookStack\Interfaces\Loggable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -class Webhook extends Model +/** + * @property int $id + * @property string $name + * @property string $endpoint + */ +class Webhook extends Model implements Loggable { use HasFactory; + + /** + * Get the string descriptor for this item. + */ + public function logDescriptor(): string + { + return "({$this->id}) {$this->name}"; + } } diff --git a/app/Http/Controllers/WebhookController.php b/app/Http/Controllers/WebhookController.php index 8745bf91d..15a31f312 100644 --- a/app/Http/Controllers/WebhookController.php +++ b/app/Http/Controllers/WebhookController.php @@ -2,15 +2,93 @@ namespace BookStack\Http\Controllers; +use BookStack\Actions\ActivityType; +use BookStack\Actions\Webhook; use Illuminate\Http\Request; class WebhookController extends Controller { + public function __construct() + { + $this->middleware([ + 'can:settings-manage', + ]); + } + /** * Show all webhooks configured in the system. */ public function index() { + // TODO - Get and pass webhooks return view('settings.webhooks.index'); } + + /** + * Show the view for creating a new webhook in the system. + */ + public function create() + { + return view('settings.webhooks.create'); + } + + /** + * Store a new webhook in the system. + */ + public function store(Request $request) + { + // TODO - Create webhook + $this->logActivity(ActivityType::WEBHOOK_CREATE, $webhook); + return redirect('/settings/webhooks'); + } + + /** + * Show the view to edit an existing webhook. + */ + public function edit(string $id) + { + /** @var Webhook $webhook */ + $webhook = Webhook::query()->findOrFail($id); + + return view('settings.webhooks.edit', ['webhook' => $webhook]); + } + + /** + * Update an existing webhook with the provided request data. + */ + public function update(Request $request, string $id) + { + /** @var Webhook $webhook */ + $webhook = Webhook::query()->findOrFail($id); + + // TODO - Update + + $this->logActivity(ActivityType::WEBHOOK_UPDATE, $webhook); + return redirect('/settings/webhooks'); + } + + /** + * Show the view to delete a webhook. + */ + public function delete(string $id) + { + /** @var Webhook $webhook */ + $webhook = Webhook::query()->findOrFail($id); + return view('settings.webhooks.delete', ['webhook' => $webhook]); + } + + /** + * Destroy a webhook from the system. + */ + public function destroy(string $id) + { + /** @var Webhook $webhook */ + $webhook = Webhook::query()->findOrFail($id); + + // TODO - Delete event type relations + $webhook->delete(); + + $this->logActivity(ActivityType::WEBHOOK_DELETE, $webhook); + return redirect('/settings/webhooks'); + } } diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 010ee04ba..fe348aba7 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -50,6 +50,7 @@ import templateManager from "./template-manager.js" import toggleSwitch from "./toggle-switch.js" import triLayout from "./tri-layout.js" import userSelect from "./user-select.js" +import webhookEvents from "./webhook-events"; import wysiwygEditor from "./wysiwyg-editor.js" const componentMapping = { @@ -105,6 +106,7 @@ const componentMapping = { "toggle-switch": toggleSwitch, "tri-layout": triLayout, "user-select": userSelect, + "webhook-events": webhookEvents, "wysiwyg-editor": wysiwygEditor, }; diff --git a/resources/js/components/webhook-events.js b/resources/js/components/webhook-events.js new file mode 100644 index 000000000..54080d36e --- /dev/null +++ b/resources/js/components/webhook-events.js @@ -0,0 +1,32 @@ + +/** + * Webhook Events + * Manages dynamic selection control in the webhook form interface. + * @extends {Component} + */ +class WebhookEvents { + + setup() { + this.checkboxes = this.$el.querySelectorAll('input[type="checkbox"]'); + this.allCheckbox = this.$refs.all; + + this.$el.addEventListener('change', event => { + if (event.target.checked && event.target === this.allCheckbox) { + this.deselectIndividualEvents(); + } else if (event.target.checked) { + this.allCheckbox.checked = false; + } + }); + } + + deselectIndividualEvents() { + for (const checkbox of this.checkboxes) { + if (checkbox !== this.allCheckbox) { + checkbox.checked = false; + } + } + } + +} + +export default WebhookEvents; \ No newline at end of file diff --git a/resources/lang/en/activities.php b/resources/lang/en/activities.php index 50bda60bd..1919df706 100644 --- a/resources/lang/en/activities.php +++ b/resources/lang/en/activities.php @@ -51,6 +51,14 @@ return [ 'mfa_setup_method_notification' => 'Multi-factor method successfully configured', 'mfa_remove_method_notification' => 'Multi-factor method successfully removed', + // Webhooks + 'webhook_create' => 'created webhook', + 'webhook_create_notification' => 'Webhook successfully created', + 'webhook_update' => 'updated webhook', + 'webhook_update_notification' => 'Webhook successfully updated', + 'webhook_delete' => 'deleted webhook', + 'webhook_delete_notification' => 'Webhook successfully deleted', + // Other 'commented_on' => 'commented on', 'permissions_update' => 'updated permissions', diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 57cbe500e..6812075b3 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -236,6 +236,19 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_create' => 'Create New Webhook', + 'webhooks_edit' => 'Edit Webhook', + 'webhooks_save' => 'Save Webhook', + 'webhooks_details' => 'Webhook Details', + 'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.', + 'webhooks_events' => 'Webhook Events', + 'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.', + 'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.', + 'webhooks_events_all' => 'All system events', + 'webhooks_name' => 'Webhook Name', + 'webhooks_endpoint' => 'Webhook Endpoint', + 'webhooks_delete' => 'Delete Webhook', + 'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.', + 'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?', //! If editing translations files directly please ignore this in all //! languages apart from en. Content will be auto-copied from en. diff --git a/resources/views/settings/webhooks/create.blade.php b/resources/views/settings/webhooks/create.blade.php new file mode 100644 index 000000000..b49afe415 --- /dev/null +++ b/resources/views/settings/webhooks/create.blade.php @@ -0,0 +1,16 @@ +@extends('layouts.simple') + +@section('body') + +
{{ trans('settings.webhooks_delete_warning', ['webhookName' => $webhook->name]) }}
+ + + +{{ trans('settings.webhooks_details_desc') }}
+{{ trans('settings.webhooks_events_desc') }}
+{{ trans('settings.webhooks_events_warning') }}
+ +