mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Completed webhook management interface
Got webhook CRUD actions in place within the interface. Quick manual test pass done, Needs automated tests.
This commit is contained in:
parent
4621d8bcc5
commit
8716b1922b
@ -3,18 +3,67 @@
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $endpoint
|
||||
* @property Collection $trackedEvents
|
||||
*/
|
||||
class Webhook extends Model implements Loggable
|
||||
{
|
||||
protected $fillable = ['name', 'endpoint'];
|
||||
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* Define the tracked event relation a webhook.
|
||||
*/
|
||||
public function trackedEvents(): HasMany
|
||||
{
|
||||
return $this->hasMany(WebhookTrackedEvent::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tracked events for a webhook from the given list of event types.
|
||||
*/
|
||||
public function updateTrackedEvents(array $events): void
|
||||
{
|
||||
$this->trackedEvents()->delete();
|
||||
|
||||
$eventsToStore = array_intersect($events, array_values(ActivityType::all()));
|
||||
if (in_array('all', $events)) {
|
||||
$eventsToStore = ['all'];
|
||||
}
|
||||
|
||||
$trackedEvents = [];
|
||||
foreach ($eventsToStore as $event) {
|
||||
$trackedEvents[] = new WebhookTrackedEvent(['event' => $event]);
|
||||
}
|
||||
|
||||
$this->trackedEvents()->saveMany($trackedEvents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this webhook tracks the given event.
|
||||
*/
|
||||
public function tracksEvent(string $event): bool
|
||||
{
|
||||
return $this->trackedEvents->pluck('event')->contains($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL for this webhook within the settings interface.
|
||||
*/
|
||||
public function getUrl(string $path = ''): string
|
||||
{
|
||||
return url('/settings/webhooks/' . $this->id . '/' . ltrim($path, '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string descriptor for this item.
|
||||
*/
|
||||
|
18
app/Actions/WebhookTrackedEvent.php
Normal file
18
app/Actions/WebhookTrackedEvent.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $webhook_id
|
||||
* @property string $event
|
||||
*/
|
||||
class WebhookTrackedEvent extends Model
|
||||
{
|
||||
protected $fillable = ['event'];
|
||||
|
||||
use HasFactory;
|
||||
}
|
@ -20,8 +20,11 @@ class WebhookController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// TODO - Get and pass webhooks
|
||||
return view('settings.webhooks.index');
|
||||
$webhooks = Webhook::query()
|
||||
->orderBy('name', 'desc')
|
||||
->with('trackedEvents')
|
||||
->get();
|
||||
return view('settings.webhooks.index', ['webhooks' => $webhooks]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,7 +40,16 @@ class WebhookController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
// TODO - Create webhook
|
||||
$validated = $this->validate($request, [
|
||||
'name' => ['required', 'max:150'],
|
||||
'endpoint' => ['required', 'url', 'max:500'],
|
||||
'events' => ['required', 'array']
|
||||
]);
|
||||
|
||||
$webhook = new Webhook($validated);
|
||||
$webhook->save();
|
||||
$webhook->updateTrackedEvents(array_values($validated['events']));
|
||||
|
||||
$this->logActivity(ActivityType::WEBHOOK_CREATE, $webhook);
|
||||
return redirect('/settings/webhooks');
|
||||
}
|
||||
@ -48,7 +60,9 @@ class WebhookController extends Controller
|
||||
public function edit(string $id)
|
||||
{
|
||||
/** @var Webhook $webhook */
|
||||
$webhook = Webhook::query()->findOrFail($id);
|
||||
$webhook = Webhook::query()
|
||||
->with('trackedEvents')
|
||||
->findOrFail($id);
|
||||
|
||||
return view('settings.webhooks.edit', ['webhook' => $webhook]);
|
||||
}
|
||||
@ -58,10 +72,17 @@ class WebhookController extends Controller
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
$validated = $this->validate($request, [
|
||||
'name' => ['required', 'max:150'],
|
||||
'endpoint' => ['required', 'url', 'max:500'],
|
||||
'events' => ['required', 'array']
|
||||
]);
|
||||
|
||||
/** @var Webhook $webhook */
|
||||
$webhook = Webhook::query()->findOrFail($id);
|
||||
|
||||
// TODO - Update
|
||||
$webhook->fill($validated)->save();
|
||||
$webhook->updateTrackedEvents($validated['events']);
|
||||
|
||||
$this->logActivity(ActivityType::WEBHOOK_UPDATE, $webhook);
|
||||
return redirect('/settings/webhooks');
|
||||
@ -85,7 +106,7 @@ class WebhookController extends Controller
|
||||
/** @var Webhook $webhook */
|
||||
$webhook = Webhook::query()->findOrFail($id);
|
||||
|
||||
// TODO - Delete event type relations
|
||||
$webhook->trackedEvents()->delete();
|
||||
$webhook->delete();
|
||||
|
||||
$this->logActivity(ActivityType::WEBHOOK_DELETE, $webhook);
|
||||
|
@ -18,6 +18,18 @@ class CreateWebhooksTable extends Migration
|
||||
$table->string('name', 150);
|
||||
$table->string('endpoint', 500);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('name');
|
||||
});
|
||||
|
||||
Schema::create('webhook_tracked_events', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('webhook_id');
|
||||
$table->string('event', 50);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('event');
|
||||
$table->index('webhook_id');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ class WebhookEvents {
|
||||
|
||||
setup() {
|
||||
this.checkboxes = this.$el.querySelectorAll('input[type="checkbox"]');
|
||||
this.allCheckbox = this.$refs.all;
|
||||
this.allCheckbox = this.$el.querySelector('input[type="checkbox"][value="all"]');
|
||||
|
||||
this.$el.addEventListener('change', event => {
|
||||
if (event.target.checked && event.target === this.allCheckbox) {
|
||||
|
@ -246,6 +246,7 @@ return [
|
||||
'webhooks_events_all' => 'All system events',
|
||||
'webhooks_name' => 'Webhook Name',
|
||||
'webhooks_endpoint' => 'Webhook Endpoint',
|
||||
'webhook_events_table_header' => 'Events',
|
||||
'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?',
|
||||
|
3
resources/views/form/errors.blade.php
Normal file
3
resources/views/form/errors.blade.php
Normal file
@ -0,0 +1,3 @@
|
||||
@if($errors->has($name))
|
||||
<div class="text-neg text-small">{{ $errors->first($name) }}</div>
|
||||
@endif
|
@ -8,7 +8,7 @@
|
||||
@include('settings.parts.navbar', ['selected' => 'webhooks'])
|
||||
</div>
|
||||
|
||||
<form action="{{ url("/settings/webhooks/new") }}" method="POST">
|
||||
<form action="{{ url("/settings/webhooks/create") }}" method="POST">
|
||||
@include('settings.webhooks.parts.form', ['title' => trans('settings.webhooks_create')])
|
||||
</form>
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@
|
||||
<p>{{ trans('settings.webhooks_delete_warning', ['webhookName' => $webhook->name]) }}</p>
|
||||
|
||||
|
||||
<form action="{{ url("/settings/webhooks/{$role->id}") }}" method="POST">
|
||||
<form action="{{ $webhook->getUrl() }}" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
{!! method_field('DELETE') !!}
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<div class="form-group text-right">
|
||||
<a href="{{ url("/settings/webhooks/{$role->id}") }}" class="button outline">{{ trans('common.cancel') }}</a>
|
||||
<a href="{{ $webhook->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
|
||||
<button type="submit" class="button">{{ trans('common.confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
@include('settings.parts.navbar', ['selected' => 'webhooks'])
|
||||
</div>
|
||||
|
||||
<form action="{{ url("/settings/webhooks/{$webhook->id}") }}" method="POST">
|
||||
<form action="{{ $webhook->getUrl() }}" method="POST">
|
||||
{!! method_field('PUT') !!}
|
||||
@include('settings.webhooks.parts.form', ['model' => $webhook, 'title' => trans('settings.webhooks_edit')])
|
||||
</form>
|
||||
|
@ -14,10 +14,40 @@
|
||||
<h1 class="list-heading">{{ trans('settings.webhooks') }}</h1>
|
||||
|
||||
<div class="text-right">
|
||||
<a href="{{ url("/settings/webhooks/create") }}" class="button outline">{{ trans('settings.webhooks_create') }}</a>
|
||||
<a href="{{ url("/settings/webhooks/create") }}"
|
||||
class="button outline">{{ trans('settings.webhooks_create') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(count($webhooks) > 0)
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{{ trans('common.name') }}</th>
|
||||
<th>{{ trans('settings.webhook_events_table_header') }}</th>
|
||||
</tr>
|
||||
@foreach($webhooks as $webhook)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ $webhook->getUrl() }}">{{ $webhook->name }}</a> <br>
|
||||
<span class="small text-muted italic">{{ $webhook->endpoint }}</span>
|
||||
</td>
|
||||
<td>
|
||||
@if($webhook->tracksEvent('all'))
|
||||
{{ trans('settings.webhooks_events_all') }}
|
||||
@else
|
||||
{{ $webhook->trackedEvents->count() }}
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
@else
|
||||
<p class="text-muted empty-text">
|
||||
{{ trans('common.no_items') }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,22 +24,32 @@
|
||||
|
||||
<div component="webhook-events">
|
||||
<label class="setting-list-label">{{ trans('settings.webhooks_events') }}</label>
|
||||
@include('form.errors', ['name' => 'events'])
|
||||
|
||||
<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 class="toggle-switch-list">
|
||||
@include('form.custom-checkbox', [
|
||||
'name' => 'events[]',
|
||||
'value' => 'all',
|
||||
'label' => trans('settings.webhooks_events_all'),
|
||||
'checked' => old('events') ? in_array('all', old('events')) : (isset($webhook) ? $webhook->tracksEvent('all') : false),
|
||||
])
|
||||
</div>
|
||||
|
||||
<hr class="my-m">
|
||||
<hr class="my-s">
|
||||
|
||||
<div class="dual-column-content">
|
||||
<div class="dual-column-content toggle-switch-list">
|
||||
@foreach(\BookStack\Actions\ActivityType::all() as $activityType)
|
||||
<label><input type="checkbox" name="events[]" value="{{ $activityType }}">{{ $activityType }}</label>
|
||||
<div>
|
||||
@include('form.custom-checkbox', [
|
||||
'name' => 'events[]',
|
||||
'value' => $activityType,
|
||||
'label' => $activityType,
|
||||
'checked' => old('events') ? in_array($activityType, old('events')) : (isset($webhook) ? $webhook->tracksEvent($activityType) : false),
|
||||
])
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@ -49,7 +59,7 @@
|
||||
<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>
|
||||
<a href="{{ $webhook->getUrl('/delete') }}" class="button outline">{{ trans('settings.webhooks_delete') }}</a>
|
||||
@endif
|
||||
<button type="submit" class="button">{{ trans('settings.webhooks_save') }}</button>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user