impl settings page

This commit is contained in:
shibafu 2020-07-23 22:26:34 +09:00
parent 35dea402ab
commit 08e12cd218
8 changed files with 138 additions and 6 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\CheckinWebhook;
use App\DeactivatedUser; use App\DeactivatedUser;
use App\Ejaculation; use App\Ejaculation;
use App\Exceptions\CsvImportException; use App\Exceptions\CsvImportException;
@ -75,6 +76,31 @@ class SettingController extends Controller
return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。'); return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。');
} }
public function webhooks()
{
$webhooks = Auth::user()->checkinWebhooks;
return view('setting.webhooks')->with(compact('webhooks'));
}
public function storeWebhooks(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255'
]);
Auth::user()->checkinWebhooks()->create($validated);
return redirect()->route('setting.webhooks')->with('status', '作成しました。');
}
public function destroyWebhooks(CheckinWebhook $webhook)
{
$webhook->delete();
return redirect()->route('setting.webhooks')->with('status', '削除しました。');
}
public function import() public function import()
{ {
return view('setting.import'); return view('setting.import');

View File

@ -16,6 +16,7 @@
"@types/bootstrap": "^4.5.0", "@types/bootstrap": "^4.5.0",
"@types/cal-heatmap": "^3.3.10", "@types/cal-heatmap": "^3.3.10",
"@types/chart.js": "^2.9.22", "@types/chart.js": "^2.9.22",
"@types/clipboard": "^2.0.1",
"@types/jquery": "^3.3.38", "@types/jquery": "^3.3.38",
"@types/js-cookie": "^2.2.0", "@types/js-cookie": "^2.2.0",
"@typescript-eslint/eslint-plugin": "^3.1.0", "@typescript-eslint/eslint-plugin": "^3.1.0",
@ -23,6 +24,7 @@
"bootstrap": "^4.5.0", "bootstrap": "^4.5.0",
"cal-heatmap": "^3.3.10", "cal-heatmap": "^3.3.10",
"chart.js": "^2.7.1", "chart.js": "^2.7.1",
"clipboard": "^2.0.6",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"date-fns": "^1.30.1", "date-fns": "^1.30.1",
"eslint": "^7.2.0", "eslint": "^7.2.0",

View File

@ -0,0 +1,29 @@
import * as ClipboardJS from 'clipboard';
$('.webhook-url').on('focus', function () {
$(this).trigger('select');
});
new ClipboardJS('.copy-to-clipboard', {
target(elem: Element): Element {
return elem.parentElement?.parentElement?.querySelector('.webhook-url') as Element;
},
}).on('success', (e) => {
e.clearSelection();
$(e.trigger).popover('show');
});
$('.copy-to-clipboard').on('shown.bs.popover', function () {
setTimeout(() => $(this).popover('hide'), 3000);
});
const $deleteModal = $('#deleteIncomingWebhookModal');
$deleteModal.find('.btn-danger').on('click', function () {
const $form = $deleteModal.find('form');
$form.attr('action', $form.attr('action')?.replace('@', $deleteModal.data('id')) || null);
$form.submit();
});
$('[data-target="#deleteIncomingWebhookModal"]').on('click', function (event) {
event.preventDefault();
$deleteModal.data('id', $(this).data('id'));
$deleteModal.modal('show', this);
});

View File

@ -10,6 +10,8 @@
href="{{ route('setting') }}"><span class="oi oi-person mr-1"></span> プロフィール</a> href="{{ route('setting') }}"><span class="oi oi-person mr-1"></span> プロフィール</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.privacy' ? 'active' : '' }}" <a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.privacy' ? 'active' : '' }}"
href="{{ route('setting.privacy') }}"><span class="oi oi-shield mr-1"></span> プライバシー</a> href="{{ route('setting.privacy') }}"><span class="oi oi-shield mr-1"></span> プライバシー</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.webhooks' ? 'active' : '' }}"
href="{{ route('setting.webhooks') }}"><span class="oi oi-link-intact mr-1"></span> Webhook</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.import' ? 'active' : '' }}" <a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.import' ? 'active' : '' }}"
href="{{ route('setting.import') }}"><span class="oi oi-data-transfer-upload mr-1"></span> データのインポート</a> href="{{ route('setting.import') }}"><span class="oi oi-data-transfer-upload mr-1"></span> データのインポート</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.export' ? 'active' : '' }}" <a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.export' ? 'active' : '' }}"

View File

@ -0,0 +1,63 @@
@extends('setting.base')
@section('title', 'Webhook')
@section('tab-content')
<h3>Incoming Webhook</h3>
<hr>
<p>さまざまなシステムと連携してチェックインを行うためのWebhook URLを作成することができます。APIドキュメントは<a href="{{ url('/apidoc.html') }}">こちら</a>から参照いただけます。</p>
<h4>新規作成</h4>
<div class="card mt-3">
<div class="card-body">
<h6 class="font-weight-bold">おことわり</h6>
<p>Webhook APIは予告なく仕様変更を行う場合がございます。また、サーバに対する過剰なリクエストや、不審な公開チェックインを繰り返している場合には管理者の裁量によって予告なく無効化(削除)する場合があります。</p>
<p>通常利用と同様、1分以内のチェックインは禁止されていることを考慮してください。また、テスト目的であれば非公開チェックインをご活用ください。</p>
<hr>
<form action="{{ route('setting.webhooks.store') }}" method="post">
{{ csrf_field() }}
<div class="form-group">
<label for="name">名前 (メモ)</label>
<input id="name" class="form-control" name="name" type="text" required>
<small class="form-text text-muted">後で分かるように名前を付けておいてください。</small>
</div>
<button class="btn btn-primary" type="submit">新規作成</button>
</form>
</div>
</div>
@if (!empty($webhooks))
<h4 class="mt-4">作成済みのWebhook</h4>
<div class="list-group mt-3">
@foreach ($webhooks as $webhook)
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="flex-grow-1 mr-2">
<div>{{ $webhook->name }}</div>
<input class="webhook-url form-control form-control-sm mt-1" type="text" value="{{ url('/api/webhooks/' . $webhook->id) }}" readonly>
</div>
<div class="ml-2">
<button class="btn btn-outline-secondary copy-to-clipboard" type="button" data-toggle="popover" data-trigger="manual" data-placement="top" data-content="コピーしました!">コピー</button>
<button class="btn btn-outline-danger" type="button" data-target="#deleteIncomingWebhookModal" data-id="{{ $webhook->id }}">削除</button>
</div>
</div>
@endforeach
</div>
@endif
@endsection
@component('components.modal', ['id' => 'deleteIncomingWebhookModal'])
@slot('title')
削除確認
@endslot
Webhookを削除してもよろしいですか
<form action="{{ route('setting.webhooks.destroy', ['webhook' => '@']) }}" method="post">
{{ csrf_field() }}
{{ method_field('DELETE') }}
</form>
@slot('footer')
<button type="button" class="btn btn-secondary" data-dismiss="modal">キャンセル</button>
<button type="button" class="btn btn-danger">削除</button>
@endslot
@endcomponent
@push('script')
<script src="{{ mix('js/setting/webhooks.js') }}"></script>
@endpush

View File

@ -39,6 +39,9 @@ Route::middleware('auth')->group(function () {
Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update'); Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update');
Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy'); Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy');
Route::post('/setting/privacy', 'SettingController@updatePrivacy')->name('setting.privacy.update'); Route::post('/setting/privacy', 'SettingController@updatePrivacy')->name('setting.privacy.update');
Route::get('/setting/webhooks', 'SettingController@webhooks')->name('setting.webhooks');
Route::post('/setting/webhooks', 'SettingController@storeWebhooks')->name('setting.webhooks.store');
Route::delete('/setting/webhooks/{webhook}', 'SettingController@destroyWebhooks')->name('setting.webhooks.destroy');
Route::get('/setting/import', 'SettingController@import')->name('setting.import'); Route::get('/setting/import', 'SettingController@import')->name('setting.import');
Route::post('/setting/import', 'SettingController@storeImport')->name('setting.import'); Route::post('/setting/import', 'SettingController@storeImport')->name('setting.import');
Route::delete('/setting/import', 'SettingController@destroyImport')->name('setting.import.destroy'); Route::delete('/setting/import', 'SettingController@destroyImport')->name('setting.import.destroy');

12
webpack.mix.js vendored
View File

@ -1,5 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mix = require('laravel-mix'); const mix = require('laravel-mix');
require('laravel-mix-bundle-analyzer') require('laravel-mix-bundle-analyzer');
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -19,18 +20,19 @@ mix.ts('resources/assets/js/app.ts', 'public/js')
.ts('resources/assets/js/setting/privacy.ts', 'public/js/setting') .ts('resources/assets/js/setting/privacy.ts', 'public/js/setting')
.ts('resources/assets/js/setting/import.ts', 'public/js/setting') .ts('resources/assets/js/setting/import.ts', 'public/js/setting')
.ts('resources/assets/js/setting/deactivate.ts', 'public/js/setting') .ts('resources/assets/js/setting/deactivate.ts', 'public/js/setting')
.ts('resources/assets/js/setting/webhooks.ts', 'public/js/setting')
.ts('resources/assets/js/checkin.ts', 'public/js') .ts('resources/assets/js/checkin.ts', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css') .sass('resources/assets/sass/app.scss', 'public/css')
.autoload({ .autoload({
'jquery': ['$', 'jQuery', 'window.jQuery'] jquery: ['$', 'jQuery', 'window.jQuery'],
}) })
.extract(['jquery', 'bootstrap']) .extract(['jquery', 'bootstrap'])
.extract(['chart.js', 'chartjs-color', 'color-name', 'moment', 'cal-heatmap', 'd3'], 'public/js/vendor/chart') .extract(['chart.js', 'chartjs-color', 'color-name', 'moment', 'cal-heatmap', 'd3'], 'public/js/vendor/chart')
.version() .version()
.webpackConfig(webpack => ({ .webpackConfig((_webpack) => ({
externals: { externals: {
moment: 'moment' moment: 'moment',
} },
})); }));
if (process.argv.includes('-a')) { if (process.argv.includes('-a')) {

View File

@ -917,6 +917,11 @@
dependencies: dependencies:
moment "^2.10.2" moment "^2.10.2"
"@types/clipboard@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/clipboard/-/clipboard-2.0.1.tgz#75a74086c293d75b12bc93ff13bc7797fef05a40"
integrity sha512-gJJX9Jjdt3bIAePQRRjYWG20dIhAgEqonguyHxXuqALxsoDsDLimihqrSg8fXgVTJ4KZCzkfglKtwsh/8dLfbA==
"@types/color-name@^1.1.1": "@types/color-name@^1.1.1":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -2199,7 +2204,7 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
clipboard@^2.0.0: clipboard@^2.0.0, clipboard@^2.0.6:
version "2.0.6" version "2.0.6"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376"
integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg== integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==