]> BookStack Code Mirror - bookstack/commitdiff
Initial controller/views for webhooks management
authorDan Brown <redacted>
Wed, 8 Dec 2021 14:29:42 +0000 (14:29 +0000)
committerDan Brown <redacted>
Wed, 8 Dec 2021 14:29:42 +0000 (14:29 +0000)
13 files changed:
app/Actions/ActivityType.php
app/Actions/Webhook.php
app/Http/Controllers/WebhookController.php
resources/js/components/index.js
resources/js/components/webhook-events.js [new file with mode: 0644]
resources/lang/en/activities.php
resources/lang/en/settings.php
resources/views/settings/webhooks/create.blade.php [new file with mode: 0644]
resources/views/settings/webhooks/delete.blade.php [new file with mode: 0644]
resources/views/settings/webhooks/edit.blade.php [new file with mode: 0644]
resources/views/settings/webhooks/index.blade.php
resources/views/settings/webhooks/parts/form.blade.php [new file with mode: 0644]
routes/web.php

index 60b1630e075121693e4084f7940bd5ac9b793fb1..8b5213a8b2af809f5e668f603a49d36f27dc1ecb 100644 (file)
@@ -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();
+    }
 }
index 6939b54d6f2d5a7b42e29797a7df8b58abbd8612..2d11584e63875ad3fb34294cb453863df2fb0295 100644 (file)
@@ -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}";
+    }
 }
index 8745bf91d1f181cb071904b7efa087b21d627b50..15a31f312fd02a185af5dc52781150e7971320de 100644 (file)
@@ -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');
+    }
 }
index 010ee04bae01f349a95ccf9d0eb8dac3f86ce051..fe348aba758a157562e2d48724f6066c22301d0e 100644 (file)
@@ -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 (file)
index 0000000..54080d3
--- /dev/null
@@ -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
index 50bda60bd294e2070a365b30047a0774aade83ae..1919df7063ab0e0e31fb186bae957312721bd431 100644 (file)
@@ -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',
index 57cbe500e89c5170f698ac34d6705803f8cc3f64..6812075b35edf60aef98ed2c8d237bb79c155b82 100755 (executable)
@@ -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 (file)
index 0000000..b49afe4
--- /dev/null
@@ -0,0 +1,16 @@
+@extends('layouts.simple')
+
+@section('body')
+
+    <div class="container small">
+
+        <div class="py-m">
+            @include('settings.parts.navbar', ['selected' => 'webhooks'])
+        </div>
+
+        <form action="{{ url("/settings/webhooks/new") }}" method="POST">
+            @include('settings.webhooks.parts.form', ['title' => trans('settings.webhooks_create')])
+        </form>
+    </div>
+
+@stop
diff --git a/resources/views/settings/webhooks/delete.blade.php b/resources/views/settings/webhooks/delete.blade.php
new file mode 100644 (file)
index 0000000..a89b011
--- /dev/null
@@ -0,0 +1,39 @@
+@extends('layouts.simple')
+
+@section('body')
+    <div class="container small">
+
+        <div class="py-m">
+            @include('settings.parts.navbar', ['selected' => 'webhooks'])
+        </div>
+
+        <div class="card content-wrap auto-height">
+            <h1 class="list-heading"> {{ trans('settings.webhooks_delete') }}</h1>
+
+            <p>{{ trans('settings.webhooks_delete_warning', ['webhookName' => $webhook->name]) }}</p>
+
+
+            <form action="{{ url("/settings/webhooks/{$role->id}") }}" method="POST">
+                {!! csrf_field() !!}
+                {!! method_field('DELETE') !!}
+
+                <div class="grid half v-center">
+                    <div>
+                        <p class="text-neg">
+                            <strong>{{ trans('settings.webhooks_delete_confirm') }}</strong>
+                        </p>
+                    </div>
+                    <div>
+                        <div class="form-group text-right">
+                            <a href="{{ url("/settings/webhooks/{$role->id}") }}" class="button outline">{{ trans('common.cancel') }}</a>
+                            <button type="submit" class="button">{{ trans('common.confirm') }}</button>
+                        </div>
+                    </div>
+                </div>
+
+
+            </form>
+        </div>
+
+    </div>
+@stop
diff --git a/resources/views/settings/webhooks/edit.blade.php b/resources/views/settings/webhooks/edit.blade.php
new file mode 100644 (file)
index 0000000..d4e60cc
--- /dev/null
@@ -0,0 +1,16 @@
+@extends('layouts.simple')
+
+@section('body')
+
+    <div class="container small">
+        <div class="py-m">
+            @include('settings.parts.navbar', ['selected' => 'webhooks'])
+        </div>
+
+        <form action="{{ url("/settings/webhooks/{$webhook->id}") }}" method="POST">
+            {!! method_field('PUT') !!}
+            @include('settings.webhooks.parts.form', ['model' => $webhook, 'title' => trans('settings.webhooks_edit')])
+        </form>
+    </div>
+
+@stop
index ca93cfeb0f4c6a9e4fda0f2ed1103e8f6399f8c5..8adf60835db6123c7a67ead5b9b1824709781334 100644 (file)
@@ -14,7 +14,7 @@
                 <h1 class="list-heading">{{ trans('settings.webhooks') }}</h1>
 
                 <div class="text-right">
-                    <a href="{{ url("/settings/webhooks/new") }}" class="button outline">{{ trans('settings.webhooks_create') }}</a>
+                    <a href="{{ url("/settings/webhooks/create") }}" class="button outline">{{ trans('settings.webhooks_create') }}</a>
                 </div>
             </div>
 
diff --git a/resources/views/settings/webhooks/parts/form.blade.php b/resources/views/settings/webhooks/parts/form.blade.php
new file mode 100644 (file)
index 0000000..935b019
--- /dev/null
@@ -0,0 +1,57 @@
+{!! csrf_field() !!}
+
+<div class="card content-wrap auto-height">
+    <h1 class="list-heading">{{ $title }}</h1>
+
+    <div class="setting-list">
+
+        <div class="grid half">
+            <div>
+                <label class="setting-list-label">{{ trans('settings.webhooks_details') }}</label>
+                <p class="small">{{ trans('settings.webhooks_details_desc') }}</p>
+            </div>
+            <div>
+                <div class="form-group">
+                    <label for="name">{{ trans('settings.webhooks_name') }}</label>
+                    @include('form.text', ['name' => 'name'])
+                </div>
+                <div class="form-group">
+                    <label for="endpoint">{{ trans('settings.webhooks_endpoint') }}</label>
+                    @include('form.text', ['name' => 'endpoint'])
+                </div>
+            </div>
+        </div>
+
+        <div component="webhook-events">
+            <label class="setting-list-label">{{ trans('settings.webhooks_events') }}</label>
+            <p class="small">{{ trans('settings.webhooks_events_desc') }}</p>
+            <p class="text-warn small">{{ trans('settings.webhooks_events_warning') }}</p>
+
+            <div>
+                <label><input type="checkbox"
+                              name="events[]"
+                              value="all"
+                              refs="webhook-events@all">
+                    {{ trans('settings.webhooks_events_all') }}</label>
+            </div>
+
+            <hr class="my-m">
+
+            <div class="dual-column-content">
+                @foreach(\BookStack\Actions\ActivityType::all() as $activityType)
+                    <label><input type="checkbox" name="events[]" value="{{ $activityType }}">{{ $activityType }}</label>
+                @endforeach
+            </div>
+        </div>
+
+    </div>
+
+    <div class="form-group text-right">
+        <a href="{{ url("/settings/webhooks") }}" class="button outline">{{ trans('common.cancel') }}</a>
+        @if ($webhook->id ?? false)
+            <a href="{{ url("/settings/roles/delete/{$webhook->id}") }}" class="button outline">{{ trans('settings.webhooks_delete') }}</a>
+        @endif
+        <button type="submit" class="button">{{ trans('settings.webhooks_save') }}</button>
+    </div>
+
+</div>
index 627ce6523971568c434e5c743d6b228214d10331..d7e734c33ed713a18b00310a428389e37d836cd0 100644 (file)
@@ -258,6 +258,12 @@ Route::middleware('auth')->group(function () {
 
     // Webhooks
     Route::get('/settings/webhooks', [WebhookController::class, 'index']);
+    Route::get('/settings/webhooks/create', [WebhookController::class, 'create']);
+    Route::post('/settings/webhooks/create', [WebhookController::class, 'store']);
+    Route::get('/settings/webhooks/{id}', [WebhookController::class, 'edit']);
+    Route::put('/settings/webhooks/{id}', [WebhookController::class, 'update']);
+    Route::get('/settings/webhooks/{id}/delete', [WebhookController::class, 'delete']);
+    Route::delete('/settings/webhooks/{id}', [WebhookController::class, 'destroy']);
 });
 
 // MFA routes