14 changed files with 502 additions and 0 deletions
@ -0,0 +1,21 @@ |
|||||||
|
MIT License |
||||||
|
|
||||||
|
Copyright (c) 2020 Dcat Admin |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
@ -0,0 +1,12 @@ |
|||||||
|
<div align="center"> |
||||||
|
<img src="http://www.dcatadmin.com/assets/img/logo-text.png" height="80"> |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
|
||||||
|
|
||||||
|
## Dcat Admin 操作日志扩展 |
||||||
|
|
||||||
|
### 安装 |
||||||
|
|
||||||
|
下载`zip`压缩包,打开扩展管理页面,点击`本地安装`按钮选择提交,然后找到`form-step`行点击`启用`按钮。 |
||||||
|
|
@ -0,0 +1,31 @@ |
|||||||
|
{ |
||||||
|
"name": "dcat-admin/operation-log", |
||||||
|
"description": "Dcat Admin 操作日志扩展", |
||||||
|
"type": "library", |
||||||
|
"keywords": ["dcat-admin", "extension"], |
||||||
|
"homepage": "https://github.com/dcat-admin/operation-log", |
||||||
|
"license": "MIT", |
||||||
|
"authors": [ |
||||||
|
{ |
||||||
|
"name": "Jiangqh", |
||||||
|
"email": "841324345@qq.com" |
||||||
|
} |
||||||
|
], |
||||||
|
"require": { |
||||||
|
"php": ">=7.1.0", |
||||||
|
"dcat/laravel-admin": "~2.0" |
||||||
|
}, |
||||||
|
"autoload": { |
||||||
|
"psr-4": { |
||||||
|
"Dcat\\Admin\\OperationLog\\": "src/" |
||||||
|
} |
||||||
|
}, |
||||||
|
"extra": { |
||||||
|
"dcat-admin": "Dcat\\Admin\\OperationLog\\OperationLogServiceProvider", |
||||||
|
"laravel": { |
||||||
|
"providers": [ |
||||||
|
"Dcat\\Admin\\OperationLog\\OperationLogServiceProvider" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
return [ |
||||||
|
'title' => 'Operation Log', |
||||||
|
'setting_title' => 'Operation Log', |
||||||
|
]; |
@ -0,0 +1,6 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
return [ |
||||||
|
'title' => '操作日志', |
||||||
|
'setting_title' => '操作日志', |
||||||
|
]; |
@ -0,0 +1,6 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
return [ |
||||||
|
'title' => '操作日志', |
||||||
|
'setting_title' => '操作日志', |
||||||
|
]; |
@ -0,0 +1,109 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Dcat\Admin\OperationLog\Http\Controllers; |
||||||
|
|
||||||
|
use Dcat\Admin\Grid; |
||||||
|
use Dcat\Admin\Http\JsonResponse; |
||||||
|
use Dcat\Admin\Layout\Content; |
||||||
|
use Dcat\Admin\OperationLog\Models\OperationLog; |
||||||
|
use Dcat\Admin\OperationLog\OperationLogServiceProvider; |
||||||
|
use Dcat\Admin\Support\Helper; |
||||||
|
use Illuminate\Support\Arr; |
||||||
|
|
||||||
|
class LogController |
||||||
|
{ |
||||||
|
public function index(Content $content) |
||||||
|
{ |
||||||
|
return $content |
||||||
|
->title(OperationLogServiceProvider::trans('log.title')) |
||||||
|
->description(trans('admin.list')) |
||||||
|
->body($this->grid()); |
||||||
|
} |
||||||
|
|
||||||
|
protected function grid() |
||||||
|
{ |
||||||
|
return new Grid(OperationLog::with('user'), function (Grid $grid) { |
||||||
|
$grid->column('id', 'ID')->sortable(); |
||||||
|
$grid->column('user', trans('admin.user')) |
||||||
|
->display(function ($user) { |
||||||
|
if (! $user) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$user = Helper::array($user); |
||||||
|
|
||||||
|
return $user['name'] ?? ($user['username'] ?? $user['id']); |
||||||
|
}) |
||||||
|
->link(function () { |
||||||
|
if ($this->user) { |
||||||
|
return admin_url('auth/users/'.$this->user['id']); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
$grid->column('method', trans('admin.method')) |
||||||
|
->label(OperationLog::$methodColors) |
||||||
|
->filterByValue(); |
||||||
|
|
||||||
|
$grid->column('path', trans('admin.uri'))->display(function ($v) { |
||||||
|
return "<code>$v</code>"; |
||||||
|
})->filterByValue(); |
||||||
|
|
||||||
|
$grid->column('ip', 'IP')->filterByValue(); |
||||||
|
|
||||||
|
$grid->column('input')->display(function ($input) { |
||||||
|
$input = json_decode($input, true); |
||||||
|
|
||||||
|
if (empty($input)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$input = Arr::except($input, ['_pjax', '_token', '_method', '_previous_']); |
||||||
|
|
||||||
|
if (empty($input)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
return '<pre class="dump" style="max-width: 500px">'.json_encode($input, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE).'</pre>'; |
||||||
|
}); |
||||||
|
|
||||||
|
$grid->column('created_at', trans('admin.created_at')); |
||||||
|
|
||||||
|
$grid->model()->orderBy('id', 'DESC'); |
||||||
|
|
||||||
|
$grid->disableCreateButton(); |
||||||
|
$grid->disableQuickEditButton(); |
||||||
|
$grid->disableEditButton(); |
||||||
|
$grid->disableViewButton(); |
||||||
|
$grid->showColumnSelector(); |
||||||
|
$grid->setActionClass(Grid\Displayers\Actions::class); |
||||||
|
|
||||||
|
$grid->filter(function (Grid\Filter $filter) { |
||||||
|
$userModel = config('admin.database.users_model'); |
||||||
|
|
||||||
|
$filter->in('user_id', trans('admin.user')) |
||||||
|
->multipleSelect($userModel::pluck('name', 'id')); |
||||||
|
|
||||||
|
$filter->equal('method', trans('admin.method')) |
||||||
|
->select( |
||||||
|
array_combine(OperationLog::$methods, OperationLog::$methods) |
||||||
|
); |
||||||
|
|
||||||
|
$filter->like('path', trans('admin.uri')); |
||||||
|
$filter->equal('ip', 'IP'); |
||||||
|
$filter->between('created_at')->datetime(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public function destroy($id) |
||||||
|
{ |
||||||
|
$ids = explode(',', $id); |
||||||
|
|
||||||
|
OperationLog::destroy(array_filter($ids)); |
||||||
|
|
||||||
|
return JsonResponse::make() |
||||||
|
->success(trans('admin.delete_succeeded')) |
||||||
|
->refresh() |
||||||
|
->send(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,160 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Dcat\Admin\OperationLog\Http\Middleware; |
||||||
|
|
||||||
|
use Dcat\Admin\Admin; |
||||||
|
use Dcat\Admin\OperationLog\Models\OperationLog as OperationLogModel; |
||||||
|
use Dcat\Admin\OperationLog\OperationLogServiceProvider; |
||||||
|
use Dcat\Admin\Support\Helper; |
||||||
|
use Illuminate\Http\Request; |
||||||
|
use Illuminate\Support\Str; |
||||||
|
|
||||||
|
class LogOperation |
||||||
|
{ |
||||||
|
protected $secretFields = [ |
||||||
|
'password', |
||||||
|
'password_confirmation', |
||||||
|
]; |
||||||
|
|
||||||
|
protected $except = [ |
||||||
|
'dcat-admin.operation-log.*', |
||||||
|
]; |
||||||
|
|
||||||
|
protected $defaultAllowedMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']; |
||||||
|
|
||||||
|
/** |
||||||
|
* Handle an incoming request. |
||||||
|
* |
||||||
|
* @param \Illuminate\Http\Request $request |
||||||
|
* @param \Closure $next |
||||||
|
* |
||||||
|
* @return mixed |
||||||
|
*/ |
||||||
|
public function handle(Request $request, \Closure $next) |
||||||
|
{ |
||||||
|
if ($this->shouldLogOperation($request)) { |
||||||
|
$user = Admin::user(); |
||||||
|
|
||||||
|
$log = [ |
||||||
|
'user_id' => $user ? $user->id : 0, |
||||||
|
'path' => substr($request->path(), 0, 255), |
||||||
|
'method' => $request->method(), |
||||||
|
'ip' => $request->getClientIp(), |
||||||
|
'input' => $this->formatInput($request->input()), |
||||||
|
]; |
||||||
|
|
||||||
|
try { |
||||||
|
OperationLogModel::create($log); |
||||||
|
} catch (\Exception $exception) { |
||||||
|
// pass |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $next($request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param array $input |
||||||
|
* |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
protected function formatInput(array $input) |
||||||
|
{ |
||||||
|
foreach ($this->getSecretFields() as $field) { |
||||||
|
if ($field && ! empty($input[$field])) { |
||||||
|
$input[$field] = Str::limit($input[$field], 3, '******'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return json_encode($input, JSON_UNESCAPED_UNICODE); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param string $key |
||||||
|
* @param mixed $default |
||||||
|
* |
||||||
|
* @return mixed |
||||||
|
*/ |
||||||
|
protected function setting($key, $default = null) |
||||||
|
{ |
||||||
|
return OperationLogServiceProvider::setting($key, $default); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param Request $request |
||||||
|
* |
||||||
|
* @return bool |
||||||
|
*/ |
||||||
|
protected function shouldLogOperation(Request $request) |
||||||
|
{ |
||||||
|
return ! $this->inExceptArray($request) |
||||||
|
&& $this->inAllowedMethods($request->method()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Whether requests using this method are allowed to be logged. |
||||||
|
* |
||||||
|
* @param string $method |
||||||
|
* |
||||||
|
* @return bool |
||||||
|
*/ |
||||||
|
protected function inAllowedMethods($method) |
||||||
|
{ |
||||||
|
$allowedMethods = collect($this->getAllowedMethods())->filter(); |
||||||
|
|
||||||
|
if ($allowedMethods->isEmpty()) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return $allowedMethods->map(function ($method) { |
||||||
|
return strtoupper($method); |
||||||
|
})->contains($method); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Determine if the request has a URI that should pass through CSRF verification. |
||||||
|
* |
||||||
|
* @param \Illuminate\Http\Request $request |
||||||
|
* |
||||||
|
* @return bool |
||||||
|
*/ |
||||||
|
protected function inExceptArray($request) |
||||||
|
{ |
||||||
|
if ($request->routeIs(admin_api_route_name('value'))) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($this->except() as $except) { |
||||||
|
if ($request->routeIs($except)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
$except = admin_base_path($except); |
||||||
|
|
||||||
|
if ($except !== '/') { |
||||||
|
$except = trim($except, '/'); |
||||||
|
} |
||||||
|
|
||||||
|
if (Helper::matchRequestPath($except)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
protected function except() |
||||||
|
{ |
||||||
|
return array_merge((array) $this->setting('except'), $this->except); |
||||||
|
} |
||||||
|
|
||||||
|
protected function getSecretFields() |
||||||
|
{ |
||||||
|
return array_merge((array) $this->setting('secret_fields'), $this->secretFields); |
||||||
|
} |
||||||
|
|
||||||
|
protected function getAllowedMethods() |
||||||
|
{ |
||||||
|
return (array) ($this->setting('allowed_methods') ?: $this->defaultAllowedMethods); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use Dcat\Admin\OperationLog\Http\Controllers; |
||||||
|
use Illuminate\Support\Facades\Route; |
||||||
|
|
||||||
|
Route::get('auth/operation-logs', Controllers\LogController::class.'@index')->name('dcat-admin.operation-log.index'); |
||||||
|
Route::delete('auth/operation-logs/{id}', Controllers\LogController::class.'@destroy')->name('dcat-admin.operation-log.destroy'); |
@ -0,0 +1,44 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Dcat\Admin\OperationLog\Models; |
||||||
|
|
||||||
|
use Dcat\Admin\Traits\HasDateTimeFormatter; |
||||||
|
use Illuminate\Database\Eloquent\Model; |
||||||
|
|
||||||
|
class OperationLog extends Model |
||||||
|
{ |
||||||
|
use HasDateTimeFormatter; |
||||||
|
|
||||||
|
protected $table = 'admin_operation_log'; |
||||||
|
|
||||||
|
protected $fillable = ['user_id', 'path', 'method', 'ip', 'input']; |
||||||
|
|
||||||
|
public static $methodColors = [ |
||||||
|
'GET' => 'primary', |
||||||
|
'POST' => 'success', |
||||||
|
'PUT' => 'blue', |
||||||
|
'DELETE' => 'danger', |
||||||
|
]; |
||||||
|
|
||||||
|
public static $methods = [ |
||||||
|
'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH', |
||||||
|
'LINK', 'UNLINK', 'COPY', 'HEAD', 'PURGE', |
||||||
|
]; |
||||||
|
|
||||||
|
public function __construct(array $attributes = []) |
||||||
|
{ |
||||||
|
$this->connection = config('database.connection') ?: config('database.default'); |
||||||
|
|
||||||
|
parent::__construct($attributes); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Log belongs to users. |
||||||
|
* |
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo |
||||||
|
*/ |
||||||
|
public function user() |
||||||
|
{ |
||||||
|
return $this->belongsTo(config('admin.database.users_model')); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Dcat\Admin\OperationLog; |
||||||
|
|
||||||
|
use Dcat\Admin\Extend\ServiceProvider; |
||||||
|
use Dcat\Admin\OperationLog\Http\Middleware\LogOperation; |
||||||
|
|
||||||
|
class OperationLogServiceProvider extends ServiceProvider |
||||||
|
{ |
||||||
|
protected $middleware = [ |
||||||
|
'middle' => [ |
||||||
|
LogOperation::class, |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
protected $menu = [ |
||||||
|
[ |
||||||
|
'title' => 'Operation Log', |
||||||
|
'uri' => 'auth/operation-logs', |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
public function settingForm() |
||||||
|
{ |
||||||
|
return new Setting($this); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Dcat\Admin\OperationLog; |
||||||
|
|
||||||
|
use Dcat\Admin\Extend\Setting as Form; |
||||||
|
use Dcat\Admin\OperationLog\Models\OperationLog; |
||||||
|
use Dcat\Admin\Support\Helper; |
||||||
|
|
||||||
|
class Setting extends Form |
||||||
|
{ |
||||||
|
public function title() |
||||||
|
{ |
||||||
|
return $this->trans('log.title'); |
||||||
|
} |
||||||
|
|
||||||
|
protected function formatInput(array $input) |
||||||
|
{ |
||||||
|
$input['except'] = Helper::array($input['except']); |
||||||
|
$input['allowed_methods'] = Helper::array($input['allowed_methods']); |
||||||
|
|
||||||
|
return $input; |
||||||
|
} |
||||||
|
|
||||||
|
public function form() |
||||||
|
{ |
||||||
|
$this->tags('except'); |
||||||
|
$this->multipleSelect('allowed_methods') |
||||||
|
->options(array_combine(OperationLog::$methods, OperationLog::$methods)); |
||||||
|
$this->tags('secret_fields'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration; |
||||||
|
use Illuminate\Database\Schema\Blueprint; |
||||||
|
use Illuminate\Support\Facades\Schema; |
||||||
|
|
||||||
|
class CreateOperationLogTable extends Migration |
||||||
|
{ |
||||||
|
public function getConnection() |
||||||
|
{ |
||||||
|
return config('database.connection') ?: config('database.default'); |
||||||
|
} |
||||||
|
|
||||||
|
public function up() |
||||||
|
{ |
||||||
|
if (! Schema::hasTable('admin_operation_log')) { |
||||||
|
Schema::create('admin_operation_log', function (Blueprint $table) { |
||||||
|
$table->bigIncrements('id')->unsigned(); |
||||||
|
$table->bigInteger('user_id'); |
||||||
|
$table->string('path'); |
||||||
|
$table->string('method', 10); |
||||||
|
$table->string('ip'); |
||||||
|
$table->text('input'); |
||||||
|
$table->index('user_id'); |
||||||
|
$table->timestamps(); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function down() |
||||||
|
{ |
||||||
|
Schema::dropIfExists('admin_operation_log'); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue