mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-17 09:52:27 +08:00
Initial controller/views for webhooks management
This commit is contained in:
@ -53,4 +53,16 @@ class ActivityType
|
|||||||
|
|
||||||
const MFA_SETUP_METHOD = 'mfa_setup_method';
|
const MFA_SETUP_METHOD = 'mfa_setup_method';
|
||||||
const MFA_REMOVE_METHOD = 'mfa_remove_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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,24 @@
|
|||||||
|
|
||||||
namespace BookStack\Actions;
|
namespace BookStack\Actions;
|
||||||
|
|
||||||
|
use BookStack\Interfaces\Loggable;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
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;
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string descriptor for this item.
|
||||||
|
*/
|
||||||
|
public function logDescriptor(): string
|
||||||
|
{
|
||||||
|
return "({$this->id}) {$this->name}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,93 @@
|
|||||||
|
|
||||||
namespace BookStack\Http\Controllers;
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
|
use BookStack\Actions\ActivityType;
|
||||||
|
use BookStack\Actions\Webhook;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class WebhookController extends Controller
|
class WebhookController extends Controller
|
||||||
{
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware([
|
||||||
|
'can:settings-manage',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show all webhooks configured in the system.
|
* Show all webhooks configured in the system.
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
|
// TODO - Get and pass webhooks
|
||||||
return view('settings.webhooks.index');
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ import templateManager from "./template-manager.js"
|
|||||||
import toggleSwitch from "./toggle-switch.js"
|
import toggleSwitch from "./toggle-switch.js"
|
||||||
import triLayout from "./tri-layout.js"
|
import triLayout from "./tri-layout.js"
|
||||||
import userSelect from "./user-select.js"
|
import userSelect from "./user-select.js"
|
||||||
|
import webhookEvents from "./webhook-events";
|
||||||
import wysiwygEditor from "./wysiwyg-editor.js"
|
import wysiwygEditor from "./wysiwyg-editor.js"
|
||||||
|
|
||||||
const componentMapping = {
|
const componentMapping = {
|
||||||
@ -105,6 +106,7 @@ const componentMapping = {
|
|||||||
"toggle-switch": toggleSwitch,
|
"toggle-switch": toggleSwitch,
|
||||||
"tri-layout": triLayout,
|
"tri-layout": triLayout,
|
||||||
"user-select": userSelect,
|
"user-select": userSelect,
|
||||||
|
"webhook-events": webhookEvents,
|
||||||
"wysiwyg-editor": wysiwygEditor,
|
"wysiwyg-editor": wysiwygEditor,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
32
resources/js/components/webhook-events.js
Normal file
32
resources/js/components/webhook-events.js
Normal file
@ -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;
|
@ -51,6 +51,14 @@ return [
|
|||||||
'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
|
'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
|
||||||
'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
|
'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
|
// Other
|
||||||
'commented_on' => 'commented on',
|
'commented_on' => 'commented on',
|
||||||
'permissions_update' => 'updated permissions',
|
'permissions_update' => 'updated permissions',
|
||||||
|
@ -236,6 +236,19 @@ return [
|
|||||||
// Webhooks
|
// Webhooks
|
||||||
'webhooks' => 'Webhooks',
|
'webhooks' => 'Webhooks',
|
||||||
'webhooks_create' => 'Create New Webhook',
|
'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
|
//! If editing translations files directly please ignore this in all
|
||||||
//! languages apart from en. Content will be auto-copied from en.
|
//! languages apart from en. Content will be auto-copied from en.
|
||||||
|
16
resources/views/settings/webhooks/create.blade.php
Normal file
16
resources/views/settings/webhooks/create.blade.php
Normal file
@ -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
|
39
resources/views/settings/webhooks/delete.blade.php
Normal file
39
resources/views/settings/webhooks/delete.blade.php
Normal file
@ -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
|
16
resources/views/settings/webhooks/edit.blade.php
Normal file
16
resources/views/settings/webhooks/edit.blade.php
Normal file
@ -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
|
@ -14,7 +14,7 @@
|
|||||||
<h1 class="list-heading">{{ trans('settings.webhooks') }}</h1>
|
<h1 class="list-heading">{{ trans('settings.webhooks') }}</h1>
|
||||||
|
|
||||||
<div class="text-right">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
57
resources/views/settings/webhooks/parts/form.blade.php
Normal file
57
resources/views/settings/webhooks/parts/form.blade.php
Normal file
@ -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>
|
@ -258,6 +258,12 @@ Route::middleware('auth')->group(function () {
|
|||||||
|
|
||||||
// Webhooks
|
// Webhooks
|
||||||
Route::get('/settings/webhooks', [WebhookController::class, 'index']);
|
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
|
// MFA routes
|
||||||
|
Reference in New Issue
Block a user