--- /dev/null
+<?php
+
+namespace BookStack\Actions;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class Webhook extends Model
+{
+ use HasFactory;
+}
/**
* Show a listing of the roles in the system.
*/
- public function list()
+ public function index()
{
$this->checkPermission('user-roles-manage');
$roles = $this->permissionsRepo->getAllRoles();
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers;
+
+use Illuminate\Http\Request;
+
+class WebhookController extends Controller
+{
+ /**
+ * Show all webhooks configured in the system.
+ */
+ public function index()
+ {
+ return view('settings.webhooks.index');
+ }
+}
--- /dev/null
+<?php
+
+namespace Database\Factories;
+
+use Illuminate\Database\Eloquent\Factories\Factory;
+
+class WebhookFactory extends Factory
+{
+ /**
+ * Define the model's default state.
+ *
+ * @return array
+ */
+ public function definition()
+ {
+ return [
+ 'name' => 'My webhook for ' . $this->faker->country(),
+ 'endpoint' => $this->faker->url,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateWebhooksTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('webhooks', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name', 150);
+ $table->string('endpoint', 500);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('webhooks');
+ }
+}
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10,15l5.88,0c0.27-0.31,0.67-0.5,1.12-0.5c0.83,0,1.5,0.67,1.5,1.5c0,0.83-0.67,1.5-1.5,1.5c-0.44,0-0.84-0.19-1.12-0.5 l-3.98,0c-0.46,2.28-2.48,4-4.9,4c-2.76,0-5-2.24-5-5c0-2.42,1.72-4.44,4-4.9l0,2.07C4.84,13.58,4,14.7,4,16c0,1.65,1.35,3,3,3 s3-1.35,3-3V15z M12.5,4c1.65,0,3,1.35,3,3h2c0-2.76-2.24-5-5-5l0,0c-2.76,0-5,2.24-5,5c0,1.43,0.6,2.71,1.55,3.62l-2.35,3.9 C6.02,14.66,5.5,15.27,5.5,16c0,0.83,0.67,1.5,1.5,1.5s1.5-0.67,1.5-1.5c0-0.16-0.02-0.31-0.07-0.45l3.38-5.63 C10.49,9.61,9.5,8.42,9.5,7C9.5,5.35,10.85,4,12.5,4z M17,13c-0.64,0-1.23,0.2-1.72,0.54l-3.05-5.07C11.53,8.35,11,7.74,11,7 c0-0.83,0.67-1.5,1.5-1.5S14,6.17,14,7c0,0.15-0.02,0.29-0.06,0.43l2.19,3.65C16.41,11.03,16.7,11,17,11l0,0c2.76,0,5,2.24,5,5 c0,2.76-2.24,5-5,5c-1.85,0-3.47-1.01-4.33-2.5l2.67,0C15.82,18.82,16.39,19,17,19c1.65,0,3-1.35,3-3S18.65,13,17,13z"/></svg>
\ No newline at end of file
'user_api_token_delete_confirm' => 'Are you sure you want to delete this API token?',
'user_api_token_delete_success' => 'API token successfully deleted',
+ // Webhooks
+ 'webhooks' => 'Webhooks',
+ 'webhooks_create' => 'Create New Webhook',
+
//! If editing translations files directly please ignore this in all
//! languages apart from en. Content will be auto-copied from en.
//!////////////////////////////////
</div>
<div class="card content-wrap auto-height">
- <h2 class="list-heading">{{ trans('settings.audit') }}</h2>
+ <h1 class="list-heading">{{ trans('settings.audit') }}</h1>
<p class="text-muted">{{ trans('settings.audit_desc') }}</p>
<div class="flex-container-row">
<div class="py-m flex fit-content">
@include('settings.parts.navbar', ['selected' => $selected])
</div>
- <div class="flex"></div>
- <div class="text-right p-m flex fit-content">
- <a target="_blank" rel="noopener noreferrer" href="https://github.com/BookStackApp/BookStack/releases">
- BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }}
- </a>
- </div>
+</div>
+<div class="px-s">
+ <hr class="darker m-none">
+</div>
+<div class="py-l px-m flex fit-content">
+ <a target="_blank" rel="noopener noreferrer" href="https://github.com/BookStackApp/BookStack/releases">
+ BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }}
+ </a>
</div>
\ No newline at end of file
@if(userCan('user-roles-manage'))
<a href="{{ url('/settings/roles') }}" @if($selected == 'roles') class="active" @endif>@icon('lock-open'){{ trans('settings.roles') }}</a>
@endif
+ @if(userCan('settings-manage'))
+ <a href="{{ url('/settings/webhooks') }}" @if($selected == 'webhooks') class="active" @endif>@icon('webhooks'){{ trans('settings.webhooks') }}</a>
+ @endif
</nav>
\ No newline at end of file
--- /dev/null
+@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">
+
+ <div class="grid half v-center">
+ <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>
+ </div>
+ </div>
+
+
+ </div>
+ </div>
+
+@stop
use BookStack\Http\Controllers\UserController;
use BookStack\Http\Controllers\UserProfileController;
use BookStack\Http\Controllers\UserSearchController;
+use BookStack\Http\Controllers\WebhookController;
+use BookStack\Http\Middleware\VerifyCsrfToken;
+use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Route;
+use Illuminate\View\Middleware\ShareErrorsFromSession;
Route::get('/status', [StatusController::class, 'show']);
Route::get('/robots.txt', [HomeController::class, 'robots']);
Route::delete('/settings/users/{userId}/api-tokens/{tokenId}', [UserApiTokenController::class, 'destroy']);
// Roles
- Route::get('/settings/roles', [RoleController::class, 'list']);
+ Route::get('/settings/roles', [RoleController::class, 'index']);
Route::get('/settings/roles/new', [RoleController::class, 'create']);
Route::post('/settings/roles/new', [RoleController::class, 'store']);
Route::get('/settings/roles/delete/{id}', [RoleController::class, 'showDelete']);
Route::delete('/settings/roles/delete/{id}', [RoleController::class, 'delete']);
Route::get('/settings/roles/{id}', [RoleController::class, 'edit']);
Route::put('/settings/roles/{id}', [RoleController::class, 'update']);
+
+ // Webhooks
+ Route::get('/settings/webhooks', [WebhookController::class, 'index']);
});
// MFA routes
Route::get('/saml2/metadata', [Auth\Saml2Controller::class, 'metadata']);
Route::get('/saml2/sls', [Auth\Saml2Controller::class, 'sls']);
Route::post('/saml2/acs', [Auth\Saml2Controller::class, 'startAcs'])->withoutMiddleware([
- \Illuminate\Session\Middleware\StartSession::class,
- \Illuminate\View\Middleware\ShareErrorsFromSession::class,
- \BookStack\Http\Middleware\VerifyCsrfToken::class,
+ StartSession::class,
+ ShareErrorsFromSession::class,
+ VerifyCsrfToken::class,
]);
Route::get('/saml2/acs', [Auth\Saml2Controller::class, 'processAcs']);