Sfoglia il codice sorgente

新系统日志模块

兔姬桑 4 anni fa
parent
commit
efa0579de7

+ 1 - 1
app/Components/Helpers.php

@@ -271,7 +271,7 @@ class Helpers
         $ipLocation = IP::getIPInfo($ip);
 
         if (empty($ipLocation) || empty($ipLocation['country'])) {
-            Log::warning(trans('error.get_ip').':'.$ip);
+            Log::warning(trans('errors.get_ip').':'.$ip);
         }
 
         $log = new UserLoginLog();

+ 10 - 10
app/Exceptions/Handler.php

@@ -77,46 +77,46 @@ class Handler extends ExceptionHandler
                     Log::warning('异常请求:'.$request->fullUrl().',IP:'.IP::getClientIp());
 
                     if ($request->ajax() || $request->wantsJson()) {
-                        return Response::json(['status' => 'fail', 'message' => trans('error.missing_page')], 404);
+                        return Response::json(['status' => 'fail', 'message' => trans('errors.missing_page')], 404);
                     }
 
-                    return Response::view('auth.error', ['message' => trans('error.missing_page')], 404);
+                    return Response::view('auth.error', ['message' => trans('errors.missing_page')], 404);
                 case $exception instanceof AuthenticationException:  // 捕获身份校验异常
                     if ($request->ajax() || $request->wantsJson()) {
-                        return Response::json(['status' => 'fail', 'message' => trans('error.unauthorized')], 401);
+                        return Response::json(['status' => 'fail', 'message' => trans('errors.unauthorized')], 401);
                     }
 
-                    return Response::view('auth.error', ['message' => trans('error.unauthorized')], 401);
+                    return Response::view('auth.error', ['message' => trans('errors.unauthorized')], 401);
                 case $exception instanceof TokenMismatchException: // 捕获CSRF异常
                     if ($request->ajax() || $request->wantsJson()) {
                         return Response::json([
                             'status'  => 'fail',
-                            'message' => trans('error.refresh_page').'<a href="'.route('login').'" target="_blank">'.trans('error.refresh').'</a>',
+                            'message' => trans('errors.refresh_page').'<a href="'.route('login').'" target="_blank">'.trans('errors.refresh').'</a>',
                         ], 419);
                     }
 
                     return Response::view(
                         'auth.error',
-                        ['message' => trans('error.refresh_page').'<a href="'.route('login').'" target="_blank">'.trans('error.refresh').'</a>'],
+                        ['message' => trans('errors.refresh_page').'<a href="'.route('login').'" target="_blank">'.trans('errors.refresh').'</a>'],
                         419
                     );
                 case $exception instanceof ReflectionException:
                     if ($request->ajax() || $request->wantsJson()) {
-                        return Response::json(['status' => 'fail', 'message' => trans('error.system')], 500);
+                        return Response::json(['status' => 'fail', 'message' => trans('errors.system')], 500);
                     }
 
-                    return Response::view('auth.error', ['message' => trans('error.system')], 500);
+                    return Response::view('auth.error', ['message' => trans('errors.system')], 500);
                 case $exception instanceof ErrorException: // 捕获系统错误异常
                     if ($request->ajax() || $request->wantsJson()) {
                         return Response::json([
                             'status'  => 'fail',
-                            'message' => trans('error.system').', '.trans('error.visit').'<a href="'.route('admin.log.viewer').'" target="_blank">'.trans('error.log').'</a>',
+                            'message' => trans('errors.system').', '.trans('errors.visit').'<a href="'.route('log-viewer::dashboard').'" target="_blank">'.trans('errors.log').'</a>',
                         ], 500);
                     }
 
                     return Response::view(
                         'auth.error',
-                        ['message' => trans('error.system').', '.trans('error.visit').'<a href="'.route('admin.log.viewer').'" target="_blank">'.trans('error.log').'</a>'],
+                        ['message' => trans('errors.system').', '.trans('errors.visit').'<a href="'.route('log-viewer::dashboard').'" target="_blank">'.trans('errors.log').'</a>'],
                         500
                     );
                 case $exception instanceof ConnectionException:

+ 9 - 9
app/Http/Controllers/User/SubscribeController.php

@@ -28,38 +28,38 @@ class SubscribeController extends Controller
         // 检查订阅码是否有效
         $subscribe = UserSubscribe::whereCode($code)->first();
         if (! $subscribe) {
-            return $this->failed(trans('error.subscribe.unknown'));
+            return $this->failed(trans('errors.subscribe.unknown'));
         }
 
         if ($subscribe->status !== 1) {
-            return $this->failed(trans('error.subscribe.sub_baned'));
+            return $this->failed(trans('errors.subscribe.sub_baned'));
         }
 
         // 检查用户是否有效
         $user = $subscribe->user;
         if (! $user) {
-            return $this->failed(trans('error.subscribe.user'));
+            return $this->failed(trans('errors.subscribe.user'));
         }
 
         if ($user->status === -1) {
-            return $this->failed(trans('error.subscribe.user_disable'));
+            return $this->failed(trans('errors.subscribe.user_disable'));
         }
 
         if ($user->enable !== 1) {
             if ($user->ban_time) {
-                return $this->failed(trans('error.subscribe.baned_until', ['time' => $user->ban_time]));
+                return $this->failed(trans('errors.subscribe.baned_until', ['time' => $user->ban_time]));
             }
 
             $unusedTraffic = $user->transfer_enable - $user->used_traffic;
             if ($unusedTraffic <= 0) {
-                return $this->failed(trans('error.subscribe.out'));
+                return $this->failed(trans('errors.subscribe.out'));
             }
 
             if ($user->expired_at < date('Y-m-d')) {
-                return $this->failed(trans('error.subscribe.expired'));
+                return $this->failed(trans('errors.subscribe.expired'));
             }
 
-            return $this->failed(trans('error.subscribe.question'));
+            return $this->failed(trans('errors.subscribe.question'));
         }
 
         // 更新访问次数
@@ -79,7 +79,7 @@ class SubscribeController extends Controller
 
         $nodeList = $query->orderByDesc('sort')->orderBy('id')->get();
         if (empty($nodeList)) {
-            return $this->failed(trans('error.subscribe.none'));
+            return $this->failed(trans('errors.subscribe.none'));
         }
 
         $servers = [];

+ 1 - 1
app/Http/Controllers/UserController.php

@@ -525,7 +525,7 @@ class UserController extends Controller
     public function switchToAdmin(): JsonResponse
     {
         if (! Session::has('admin')) {
-            return Response::json(['status' => 'fail', 'message' => trans('error.unauthorized')]);
+            return Response::json(['status' => 'fail', 'message' => trans('errors.unauthorized')]);
         }
 
         // 管理员信息重新写入user

+ 5 - 5
app/Http/Middleware/isForbidden.php

@@ -24,7 +24,7 @@ class isForbidden
         if (sysConfig('is_forbid_robot') && Agent::isRobot()) {
             Log::warning('识别到机器人访问('.IP::getClientIp().')');
 
-            return Response::view('auth.error', ['message' => trans('error.forbidden.bots')], 403);
+            return Response::view('auth.error', ['message' => trans('errors.forbidden.bots')], 403);
         }
 
         // 拒绝通过订阅链接域名访问网站,防止网站被探测
@@ -39,7 +39,7 @@ class isForbidden
 
         // 拒绝无IP请求
         if (! $ipLocation || empty(array_filter($ipLocation))) {
-            return Response::view('auth.error', ['message' => trans('error.forbidden.access')], 403);
+            return Response::view('auth.error', ['message' => trans('errors.forbidden.access')], 403);
         }
 
         if (! in_array($ipLocation['country'], ['本机地址', '局域网']) && sysConfig('forbid_mode')) {
@@ -49,21 +49,21 @@ class isForbidden
                     if (in_array($ipLocation['country'], ['China', '中国']) && ! in_array($ipLocation['province'], ['香港', '澳门', '台湾', '台湾省'])) {
                         Log::warning('识别到大陆IP,拒绝访问:'.$ip);
 
-                        return Response::view('auth.error', ['message' => trans('error.forbidden.china')], 403);
+                        return Response::view('auth.error', ['message' => trans('errors.forbidden.china')], 403);
                     }
                     break;
                 case 'ban_china':
                     if (in_array($ipLocation['country'], ['China', '中国', 'Taiwan', 'Hong Kong', 'Macao'])) {
                         Log::warning('识别到中国IP,拒绝访问:'.$ip);
 
-                        return Response::view('auth.error', ['message' => trans('error.forbidden.china')], 403);
+                        return Response::view('auth.error', ['message' => trans('errors.forbidden.china')], 403);
                     }
                     break;
                 case 'ban_oversea':
                     if (! in_array($ipLocation['country'], ['China', '中国', 'Taiwan', 'Hong Kong', 'Macao'])) {
                         Log::warning('识别到海外IP,拒绝访问:'.$ip.' - '.$ipLocation['country']);
 
-                        return Response::view('auth.error', ['message' => trans('error.forbidden.oversea')], 403);
+                        return Response::view('auth.error', ['message' => trans('errors.forbidden.oversea')], 403);
                     }
                     break;
                 default:

+ 1 - 1
app/Http/Middleware/isSecurity.php

@@ -26,7 +26,7 @@ class isSecurity
 
         if ($websiteSecurityCode && ! Cache::has($cacheKey)) {
             if ($code !== $websiteSecurityCode) {
-                Log::warning(trans('error.unsafe_enter').$ip);
+                Log::warning(trans('errors.unsafe_enter').$ip);
 
                 return Response::view('auth.safe');
             }

+ 3 - 3
composer.json

@@ -9,10 +9,12 @@
   "license": "GPL-3.0-or-later",
   "require": {
     "php": "^7.2.5",
+    "ext-dom": "*",
     "ext-json": "*",
     "ext-mbstring": "*",
     "ext-openssl": "*",
     "ext-simplexml": "*",
+    "arcanedev/log-viewer": "~7.0",
     "doctrine/dbal": "^2.12",
     "fideloper/proxy": "^4.4",
     "fruitcake/laravel-cors": "^2.0",
@@ -32,7 +34,6 @@
     "mews/purifier": "^3.3",
     "overtrue/laravel-lang": "~5.0",
     "phpoffice/phpspreadsheet": "^1.16",
-    "rap2hpoutre/laravel-log-viewer": "^1.7",
     "riverslei/payment": "^5.1",
     "socialiteproviders/telegram": "^4.1",
     "spatie/laravel-permission": "^4.0",
@@ -44,8 +45,7 @@
     "zbrettonye/geetest": "^1.2",
     "zbrettonye/hcaptcha": "^1.1",
     "zbrettonye/no-captcha": "^1.1",
-    "zoujingli/ip2region": "^1.0",
-    "ext-dom": "*"
+    "zoujingli/ip2region": "^1.0"
   },
   "require-dev": {
     "arcanedev/laravel-lang": "^8.0",

File diff suppressed because it is too large
+ 276 - 265
composer.lock


+ 136 - 0
config/log-viewer.php

@@ -0,0 +1,136 @@
+<?php
+
+use Arcanedev\LogViewer\Contracts\Utilities\Filesystem;
+
+return [
+
+    /* -----------------------------------------------------------------
+     |  Log files storage path
+     | -----------------------------------------------------------------
+     */
+
+    'storage-path' => storage_path('logs'),
+
+    /* -----------------------------------------------------------------
+     |  Log files pattern
+     | -----------------------------------------------------------------
+     */
+
+    'pattern' => [
+        'prefix'    => Filesystem::PATTERN_PREFIX,    // 'laravel-'
+        'date'      => Filesystem::PATTERN_DATE,      // '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
+        'extension' => Filesystem::PATTERN_EXTENSION, // '.log'
+    ],
+
+    /* -----------------------------------------------------------------
+     |  Locale
+     | -----------------------------------------------------------------
+     |  Supported locales :
+     |    'auto', 'ar', 'bg', 'de', 'en', 'es', 'et', 'fa', 'fr', 'hu', 'hy', 'id', 'it', 'ja', 'ko', 'nl',
+     |    'pl', 'pt-BR', 'ro', 'ru', 'sv', 'th', 'tr', 'zh-TW', 'zh'
+     */
+
+    'locale' => 'auto',
+
+    /* -----------------------------------------------------------------
+     |  Theme
+     | -----------------------------------------------------------------
+     |  Supported themes :
+     |    'bootstrap-3', 'bootstrap-4'
+     |  Make your own theme by adding a folder to the views directory and specifying it here.
+     */
+
+    'theme' => 'remark',
+
+    /* -----------------------------------------------------------------
+     |  Route settings
+     | -----------------------------------------------------------------
+     */
+
+    'route' => [
+        'enabled' => true,
+
+        'attributes' => [
+            'prefix'     => 'admin/log-viewer',
+            'middleware' => env('ARCANEDEV_LOGVIEWER_MIDDLEWARE') ? explode(',', env('ARCANEDEV_LOGVIEWER_MIDDLEWARE')) : ['web', 'admin'],
+        ],
+    ],
+
+    /* -----------------------------------------------------------------
+     |  Log entries per page
+     | -----------------------------------------------------------------
+     |  This defines how many logs & entries are displayed per page.
+     */
+
+    'per-page' => 30,
+
+    /* -----------------------------------------------------------------
+     |  Download settings
+     | -----------------------------------------------------------------
+     */
+
+    'download' => [
+        'prefix' => 'laravel-',
+
+        'extension' => 'log',
+    ],
+
+    /* -----------------------------------------------------------------
+     |  Menu settings
+     | -----------------------------------------------------------------
+     */
+
+    'menu' => [
+        'filter-route' => 'log-viewer::logs.filter',
+
+        'icons-enabled' => true,
+    ],
+
+    /* -----------------------------------------------------------------
+     |  Icons
+     | -----------------------------------------------------------------
+     */
+
+    'icons' => [
+        'all'       => 'fas fa-fw fa-list-ul',
+        'emergency' => 'far fa-fw fa-life-ring',
+        'alert'     => 'fas fa-fw fa-bullhorn',
+        'critical'  => 'fas fa-fw fa-heartbeat',
+        'error'     => 'fas fa-fw fa-times-circle',
+        'warning'   => 'fas fa-fw fa-exclamation-triangle',
+        'notice'    => 'fas fa-fw fa-exclamation-circle',
+        'info'      => 'fas fa-fw fa-info-circle',
+        'debug'     => 'fas fa-fw fa-bug',
+    ],
+
+    /* -----------------------------------------------------------------
+     |  Colors
+     | -----------------------------------------------------------------
+     */
+
+    'colors' => [
+        'levels' => [
+            'empty'     => '#D1D1D1',
+            'all'       => '#8A8A8A',
+            'emergency' => '#E62020',
+            'alert'     => '#FF4C52',
+            'critical'  => '#FF666B',
+            'error'     => '#F57D1B',
+            'warning'   => '#FCB900',
+            'notice'    => '#589FFC',
+            'info'      => '#28C0DE',
+            'debug'     => '#526069',
+        ],
+    ],
+
+    /* -----------------------------------------------------------------
+     |  Strings to highlight in stack trace
+     | -----------------------------------------------------------------
+     */
+
+    'highlight' => [
+        '^#\d+',
+        '^Stack trace:',
+    ],
+
+];

+ 35 - 0
database/migrations/2021_11_25_211107_change_log_permission.php

@@ -0,0 +1,35 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Spatie\Permission\Models\Permission;
+
+class ChangeLogPermission extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        $permssion = Permission::whereName('admin.log.viewer')->first();
+        if ($permssion) {
+            $permssion->name = 'log-viewer::dashboard,log-viewer::logs.*';
+            $permssion->save();
+        }
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        $permssion = Permission::whereName('log-viewer::dashboard,log-viewer::logs.*')->first();
+        if ($permssion) {
+            $permssion->name = 'admin.log.viewer';
+            $permssion->save();
+        }
+    }
+}

+ 0 - 0
resources/lang/en/error.php → resources/lang/en/errors.php


+ 0 - 0
resources/lang/zh_CN/error.php → resources/lang/zh_CN/errors.php


+ 1 - 1
resources/views/admin/article/create.blade.php

@@ -61,7 +61,7 @@
                         <label class="col-form-label col-md-2" for="logo">LOGO</label>
                         <div class="col-md-4" id="icon" style="display: none;">
                             <input type="text" name="logo" id="logo" class="form-control" value="{{ old('logo') }}"/>
-                            <span class="text-help"><a href="https://fontawesome.com/v4.7.0/icons/" target="_blank">图标列表</a> | 格式: fa-windows</span>
+                            <span class="text-help"><a href="https://fontawesome.com/v5.15/icons/" target="_blank">图标列表</a> | 格式: fa-windows</span>
                         </div>
 
                         <div class="col-md-4" id="logoUpload">

+ 1 - 1
resources/views/admin/article/edit.blade.php

@@ -87,7 +87,7 @@
                                         @endif
                                         <input type="text" class="form-control" name="logo" value="{{$article->logo}}"/>
                                     </div>
-                                    <span class="text-help"><a href="https://fontawesome.com/v4.7.0/icons/" target="_blank">图标列表</a> | 格式: fa-windows</span>
+                                    <span class="text-help"><a href="https://fontawesome.com/v5.15/icons/" target="_blank">图标列表</a> | 格式: fa-windows</span>
                                 </div>
                             @endif
                         </div>

+ 3 - 3
resources/views/admin/layouts.blade.php

@@ -413,9 +413,9 @@
                                     </a>
                                 </li>
                             @endcan
-                            @can('admin.log.viewer')
-                                <li class="site-menu-item {{request()->routeIs('admin.log.viewer') ? 'active open' : ''}}">
-                                    <a href="{{route('admin.log.viewer')}}" target="_blank">
+                            @can('log-viewer')
+                                <li class="site-menu-item">
+                                    <a href="{{route('log-viewer::dashboard')}}" target="_blank">
                                         <span class="site-menu-title">系统运行</span>
                                     </a>
                                 </li>

+ 3 - 3
resources/views/auth/error.blade.php

@@ -1,5 +1,5 @@
 @extends('_layout')
-@section('title', sysConfig('website_name').' - '.trans('error.title'))
+@section('title', sysConfig('website_name').' - '.trans('errors.title'))
 @section('layout_css')
     <link href="/assets/css/errors.min.css" rel="stylesheet">
 @endsection
@@ -8,9 +8,9 @@
     <div class="page vertical-align text-center" data-animsition-in="fade-in" data-animsition-out="fade-out">
         <div class="page-content vertical-align-middle">
             <header>
-                <h1 class="animation-slide-top">{{trans('error.whoops')}}</h1>
+                <h1 class="animation-slide-top">{{trans('errors.whoops')}}</h1>
             </header>
-            <h3>{{trans('error.report')}}</h3>
+            <h3>{{trans('errors.report')}}</h3>
             <code class="error-advise">{!! $message !!}</code>
         </div>
     </div>

+ 2 - 2
resources/views/auth/safe.blade.php

@@ -1,12 +1,12 @@
 @extends('auth.layouts')
-@section('title', sysConfig('website_name').' - '.trans('error.safe_enter'))
+@section('title', sysConfig('website_name').' - '.trans('errors.safe_enter'))
 @section('content')
 <form role="form" action="/login?securityCode=">
     <div class="form-group">
         <div class="form-group form-material floating" data-plugin="formMaterial">
             <input class="form-control" id="securityCode" name="securityCode"
                 placeholder="" type="text">
-            <label class="floating-label" for="securityCode">{{trans('error.safe_code')}}</label>
+            <label class="floating-label" for="securityCode">{{trans('errors.safe_code')}}</label>
         </div>
     </div>
     <div class="text-center">

+ 57 - 0
resources/views/vendor/log-viewer/remark/dashboard.blade.php

@@ -0,0 +1,57 @@
+@extends('vendor.log-viewer.remark.layouts')
+
+@section('content')
+    <div class="page-header">
+        <h1>@lang('Dashboard')</h1>
+    </div>
+
+    <div class="row">
+        <div class="col-12 col-md-6 col-lg-3 col-xl-2">
+            <div style="max-height:25vh">
+                <canvas id="stats-doughnut-chart"></canvas>
+            </div>
+        </div>
+
+        <div class="col-12 col-md-6 col-lg-9 col-xl-10">
+            <div class="row">
+                @foreach($percents as $level => $item)
+                    <div class="col-sm-6 col-md-12 col-lg-4 mb-10">
+                        <div class="box level-{{ $level }} {{ $item['count'] === 0 ? 'empty' : '' }}">
+                            <div class="box-icon">
+                                {!! log_styler()->icon($level) !!}
+                            </div>
+
+                            <div class="box-content">
+                                <span class="box-text">{{ $item['name'] }}</span>
+                                <span class="box-number">
+                                    {{ $item['count'] }} @lang('entries') - {!! $item['percent'] !!} %
+                                </span>
+                                <div class="progress" style="height: 3px;">
+                                    <div class="progress-bar" style="width: {{ $item['percent'] }}%"></div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                @endforeach
+            </div>
+        </div>
+    </div>
+@endsection
+
+@section('javascript')
+    <script>
+        new Chart(document.getElementById('stats-doughnut-chart'),
+            {
+                type: 'doughnut',
+                data: {!! $chartData !!},
+                options: {
+                    plugins:{
+                        legend: {
+                            position: 'bottom',
+                        },
+                    }
+                },
+            },
+        );
+    </script>
+@endsection

+ 213 - 0
resources/views/vendor/log-viewer/remark/layouts.blade.php

@@ -0,0 +1,213 @@
+@extends('_layout')
+@section('title', sysConfig('website_name'))
+@section('layout_css')
+    <link href="/assets/global/fonts/font-awesome/css/all.min.css" rel="stylesheet">
+    <style>
+        .page-header {
+            border-bottom: 1px solid #8a8a8a;
+            margin-bottom: 20px;
+        }
+
+        /*
+         * Boxes
+         */
+
+        .box {
+            display: block;
+            padding: 0;
+            min-height: 70px;
+            background: #fff;
+            width: 100%;
+            box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+            border-radius: 1.25rem;
+        }
+
+        .box > .box-icon > i,
+        .box .box-content .box-text,
+        .box .box-content .box-number {
+            color: #FFF;
+            text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
+        }
+
+        .box > .box-icon {
+            border-radius: 1.25rem 0 0 1.25rem;
+            display: block;
+            float: left;
+            height: 70px;
+            width: 70px;
+            text-align: center;
+            font-size: 40px;
+            line-height: 70px;
+            background: rgba(0, 0, 0, 0.2);
+        }
+
+        .box .box-content {
+            padding: 5px 10px;
+            margin-left: 70px;
+        }
+
+        .box .box-content .box-text {
+            display: block;
+            font-size: 1rem;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            font-weight: 600;
+        }
+
+        .box .box-content .box-number {
+            display: block;
+        }
+
+        .box .box-content .progress {
+            background: rgba(0, 0, 0, 0.2);
+            margin: 5px -10px 5px -10px;
+        }
+
+        .box .box-content .progress .progress-bar {
+            background-color: #FFF;
+        }
+
+        /*
+         * Log Entry
+         */
+
+        .stack-content {
+            color: #AE0E0E;
+            font-family: consolas, Menlo, Courier, monospace;
+            white-space: pre-line;
+            font-size: .8rem;
+        }
+
+        /*
+         * Colors: Badge & Infobox
+         */
+
+        .badge.badge-env,
+        .badge.badge-level-all,
+        .badge.badge-level-emergency,
+        .badge.badge-level-alert,
+        .badge.badge-level-critical,
+        .badge.badge-level-error,
+        .badge.badge-level-warning,
+        .badge.badge-level-notice,
+        .badge.badge-level-info,
+        .badge.badge-level-debug,
+        .badge.empty {
+            color: #FFF;
+        }
+
+        .badge.badge-level-all,
+        .box.level-all {
+            background-color: {{ log_styler()->color('all') }};
+        }
+
+        .badge.badge-level-emergency,
+        .box.level-emergency {
+            background-color: {{ log_styler()->color('emergency') }};
+        }
+
+        .badge.badge-level-alert,
+        .box.level-alert {
+            background-color: {{ log_styler()->color('alert') }};
+        }
+
+        .badge.badge-level-critical,
+        .box.level-critical {
+            background-color: {{ log_styler()->color('critical') }};
+        }
+
+        .badge.badge-level-error,
+        .box.level-error {
+            background-color: {{ log_styler()->color('error') }};
+        }
+
+        .badge.badge-level-warning,
+        .box.level-warning {
+            background-color: {{ log_styler()->color('warning') }};
+        }
+
+        .badge.badge-level-notice,
+        .box.level-notice {
+            background-color: {{ log_styler()->color('notice') }};
+        }
+
+        .badge.badge-level-info,
+        .box.level-info {
+            background-color: {{ log_styler()->color('info') }};
+        }
+
+        .badge.badge-level-debug,
+        .box.level-debug {
+            background-color: {{ log_styler()->color('debug') }};
+        }
+
+        .badge.empty,
+        .box.empty {
+            background-color: {{ log_styler()->color('empty') }};
+        }
+
+        #entries {
+            overflow-wrap: anywhere;
+        }
+    </style>
+    @yield('css')
+@endsection
+@section('layout_content')
+    <nav class="site-navbar navbar navbar-default navbar-fixed-top navbar-mega {{config('theme.navbar.inverse')}} {{config('theme.navbar.skin')}}" role="navigation">
+        <div class="navbar-header">
+            <div class="navbar-brand navbar-brand-center">
+                <img src="{{sysConfig('website_logo')? asset(sysConfig('website_logo')) :'/assets/images/logo64.png'}}" class="navbar-brand-logo" alt="logo"/>
+                <span class="navbar-brand-text hidden-xs-down"> {{sysConfig('website_name')}}</span>
+            </div>
+        </div>
+        <div class="navbar-container container-fluid">
+            <ul class="nav navbar-toolbar">
+                <li class="nav-item {{ Route::is('log-viewer::dashboard') ? 'active' : '' }}">
+                    <a class="nav-link" href="{{ route('log-viewer::dashboard') }}" role="button">
+                        <i class="wb-pie-chart"></i> @lang('Dashboard')
+                    </a>
+                </li>
+                <li class="nav-item {{ Route::is('log-viewer::logs.list') ? 'active' : '' }}">
+                    <a class="nav-link" href="{{ route('log-viewer::logs.list') }}" role="button">
+                        <i class="wb-inbox"></i> @lang('Logs')
+                    </a>
+                </li>
+            </ul>
+            <ul class="nav navbar-toolbar navbar-right navbar-toolbar-right">
+                <li class="nav-item {{ Route::is('admin.index') ? 'active' : '' }}">
+                    <a class="nav-link" href="{{ route('admin.index') }}" role="button">
+                        <i class="wb-dashboard"></i> @lang('user.menu.admin_dashboard')
+                    </a>
+                </li>
+            </ul>
+        </div>
+    </nav>
+    <div class="page ml-0">
+        <!--[if lt IE 8]>
+        <p class="browserupgrade">您正在使用 <strong>过时/老旧</strong> 的浏览器。 为了您的使用体验,请
+            <a href="http://browsehappy.com/">升级您的浏览器</a> <br/>You are using an <strong>outdated</strong> browser. Please
+            <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
+        </p>
+        <![endif]-->
+        <div class="container-fluid">
+            @yield('content')
+        </div>
+    </div>
+    <footer class="site-footer">
+        <div class="site-footer-legal">
+            © 2017 - 2021 <a href="https://github.com/ProxyPanel/ProxyPanel" target="_blank">{{config('version.name')}} </a> {{__('All rights reserved
+            .')}}
+        </div>
+        <div class="site-footer-right">
+            Base on <a href="https://github.com/ARCANEDEV/LogViewer" target="_blank">LogViewer</a> 🚀 Version: <code> {{ log_viewer()->version() }} </code>
+        </div>
+    </footer>
+@yield('modals')
+@endsection
+@section('layout_javascript')
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.0/chart.min.js"
+            integrity="sha512-GMGzUEevhWh8Tc/njS0bDpwgxdCJLQBWG3Z2Ct+JGOpVnEmjvNx6ts4v6A2XJf1HOrtOsfhv3hBKpK9kE5z8AQ==" crossorigin="anonymous"
+            referrerpolicy="no-referrer"></script>
+    @yield('javascript')
+@endsection

+ 149 - 0
resources/views/vendor/log-viewer/remark/logs.blade.php

@@ -0,0 +1,149 @@
+@extends('vendor.log-viewer.remark.layouts')
+
+<?php /** @var  Illuminate\Pagination\LengthAwarePaginator  $rows */ ?>
+
+@section('content')
+    <div class="page-header">
+        <h1>@lang('Logs')</h1>
+    </div>
+
+    <div class="table-responsive">
+        <table class="table table-hover">
+            <thead>
+                <tr>
+                    @foreach($headers as $key => $header)
+                    <th scope="col" class="{{ $key == 'date' ? 'text-left' : 'text-center' }}">
+                        @if ($key == 'date')
+                            <strong>{{ $header }}</strong>
+                        @else
+                            <strong class="badge badge-level-{{ $key }}">
+                                {{ log_styler()->icon($key) }} {{ $header }}
+                            </strong>
+                        @endif
+                    </th>
+                    @endforeach
+                    <th scope="col" class="text-right">@lang('Actions')</th>
+                </tr>
+            </thead>
+            <tbody>
+                @forelse($rows as $date => $row)
+                    <tr>
+                        @foreach($row as $key => $value)
+                            <td class="{{ $key == 'date' ? 'text-left' : 'text-center' }}">
+                                @if ($key == 'date')
+                                    <strong>{{ $value }}</strong>
+                                @elseif ($value == 0)
+                                @else
+                                    <a href="{{ route('log-viewer::logs.filter', [$date, $key]) }}">
+                                        <span class="badge badge-level-{{ $key }}">{{ $value }}</span>
+                                    </a>
+                                @endif
+                            </td>
+                        @endforeach
+                        <td class="text-right">
+                            <a href="{{ route('log-viewer::logs.show', [$date]) }}" class="btn btn-sm btn-info">
+                                <i class="fas fa-fw fa-search"></i>
+                            </a>
+                            <a href="{{ route('log-viewer::logs.download', [$date]) }}" class="btn btn-sm btn-success">
+                                <i class="fas fa-download"></i>
+                            </a>
+                            <button class="btn btn-sm btn-danger" data-target="#deleteLogModal" data-toggle="modal" data-log-date="{{ $date }}" type="button">
+                                <i class="far fa-trash-alt"></i> @lang('Delete')
+                            </button>
+                        </td>
+                    </tr>
+                @empty
+                    <tr>
+                        <td colspan="11" class="text-center">
+                            <span class="badge badge-secondary">@lang('The list of logs is empty!')</span>
+                        </td>
+                    </tr>
+                @endforelse
+            </tbody>
+        </table>
+    </div>
+
+    {{ $rows->render() }}
+@endsection
+
+@section('modals')
+    {{-- DELETE MODAL --}}
+    <div id="deleteLogModal" class="modal fade" aria-hidden="true" aria-labelledby="deleteLogModal"
+         role="dialog" tabindex="-1">
+        <div class="modal-dialog modal-simple modal-center">
+            <form id="deleteLogForm" class="modal-content" action="{{ route('log-viewer::logs.delete') }}" method="POST">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                    <h4 class="modal-title">@lang('Delete log file')</h4>
+                </div>
+                <div class="modal-body">
+                    <input type="hidden" name="_method" value="DELETE">@csrf
+                    <input type="hidden" name="date" value="">
+                    <p></p>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary mr-auto" data-dismiss="modal">@lang('Cancel')</button>
+                    <button type="submit" class="btn btn-danger" data-loading-text="@lang('Loading')&hellip;">@lang('Delete')</button>
+                </div>
+            </form>
+        </div>
+    </div>
+@endsection
+
+@section('javascript')
+    <script>
+        $(function () {
+            const deleteLogModal = $('div#deleteLogModal'),
+                deleteLogForm = $('form#deleteLogForm'),
+                submitBtn = deleteLogForm.find('button[type=submit]');
+
+            $("button[data-target='#deleteLogModal']").on('click', function(event) {
+                event.preventDefault();
+                const date = $(this).data('log-date'),
+                    message = "{{ __('Are you sure you want to delete this log file: :date ?') }}";
+
+                deleteLogForm.find('input[name=date]').val(date);
+                deleteLogModal.find('.modal-body p').html(message.replace(':date', date));
+
+                deleteLogModal.modal('show');
+            });
+
+            deleteLogForm.on('submit', function(event) {
+                event.preventDefault();
+                submitBtn.button('loading');
+
+                $.ajax({
+                    url:      $(this).attr('action'),
+                    type:     $(this).attr('method'),
+                    dataType: 'json',
+                    data:     $(this).serialize(),
+                    success: function(data) {
+                        submitBtn.button('reset');
+                        if (data.result === 'success') {
+                            deleteLogModal.modal('hide');
+                            location.reload();
+                        }
+                        else {
+                            alert('AJAX ERROR ! Check the console !');
+                            console.error(data);
+                        }
+                    },
+                    error: function(xhr, textStatus, errorThrown) {
+                        alert('AJAX ERROR ! Check the console !');
+                        console.error(errorThrown);
+                        submitBtn.button('reset');
+                    }
+                });
+
+                return false;
+            });
+
+            deleteLogModal.on('hidden.bs.modal', function() {
+                deleteLogForm.find('input[name=date]').val('');
+                deleteLogModal.find('.modal-body p').html('');
+            });
+        });
+    </script>
+@endsection

+ 288 - 0
resources/views/vendor/log-viewer/remark/show.blade.php

@@ -0,0 +1,288 @@
+<?php
+/**
+ * @var  Arcanedev\LogViewer\Entities\Log $log
+ * @var  Illuminate\Pagination\LengthAwarePaginator $entries
+ * @var  string|null $query
+ */
+?>
+
+@extends('vendor.log-viewer.remark.layouts')
+
+@section('content')
+    <div class="page-header mb-4">
+        <h1>@lang('Log') [{{ $log->date }}]</h1>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-2">
+            {{-- Log Menu --}}
+            <div class="list-group">
+                @foreach($log->menu() as $levelKey => $item)
+                    @if ($item['count'] === 0)
+                        <a class="list-group-item disabled">
+                            <span class="level-name">{!! $item['icon'] !!} {{ $item['name'] }}</span>
+                            <span class="float-right badge empty">{{ $item['count'] }}</span>
+                        </a>
+                    @else
+                        <a href="{{ $item['url'] }}"
+                           class="list-group-item level-{{ $levelKey }}{{ $level === $levelKey ? ' active' : ''}}">
+                            <span class="level-name">{!! $item['icon'] !!} {{ $item['name'] }}</span>
+                            <span class="float-right badge badge-level-{{ $levelKey }}">{{ $item['count'] }}</span>
+                        </a>
+                    @endif
+                @endforeach
+            </div>
+        </div>
+        <div class="col-lg-10">
+            {{-- Log Details --}}
+            <div class="panel">
+                <div class="panel-heading">
+                    <h3 class="panel-title"> @lang('Log info') :</h3>
+                    <div class="panel-actions panel-actions-keep">
+                        <a href="{{ route('log-viewer::logs.download', [$log->date]) }}" class="btn btn-sm btn-success">
+                            <i class="fas fa-download"></i> @lang('Download')
+                        </a>
+                        <button class="btn btn-sm btn-danger" data-target="#deleteLogModal" data-toggle="modal" type="button">
+                            <i class="far fa-trash-alt"></i> @lang('Delete')
+                        </button>
+                    </div>
+                </div>
+                <div class="panel-body">
+                    <div class="table-responsive">
+                        <table class="table mb-0">
+                            <tbody>
+                            <tr>
+                                <td>@lang('File path') :</td>
+                                <td colspan="7">{{ $log->getPath() }}</td>
+                            </tr>
+                            <tr>
+                                <td>@lang('Log entries') :</td>
+                                <td>
+                                    <span class="badge badge-primary">{{ $entries->total() }}</span>
+                                </td>
+                                <td>@lang('Size') :</td>
+                                <td>
+                                    <span class="badge badge-primary">{{ $log->size() }}</span>
+                                </td>
+                                <td>@lang('Created at') :</td>
+                                <td>
+                                    <span class="badge badge-primary">{{ $log->createdAt() }}</span>
+                                </td>
+                                <td>@lang('Updated at') :</td>
+                                <td>
+                                    <span class="badge badge-primary">{{ $log->updatedAt() }}</span>
+                                </td>
+                            </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+                <div class="panel-footer">
+                    {{-- Search --}}
+                    <form action="{{ route('log-viewer::logs.search', [$log->date, $level]) }}" method="GET">
+                        <div class="form-group">
+                            <div class="input-group">
+                                <input id="query" name="query" class="form-control" value="{{ $query }}" placeholder="@lang('Type here to search')">
+                                <div class="input-group-append">
+                                    @unless (is_null($query))
+                                        <a href="{{ route('log-viewer::logs.show', [$log->date]) }}" class="btn btn-secondary">
+                                            (@lang(':count results', ['count' => $entries->count()])) <i class="fa fa-fw fa-times"></i>
+                                        </a>
+                                    @endunless
+                                    <button id="search-btn" class="btn btn-primary">
+                                        <i class="fas fa-fw fa-search"></i>
+                                    </button>
+                                </div>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+            {{-- Log Entries --}}
+            <div class="panel">
+                <div class="panel-heading">
+                    <h3 class="panel-title"> @lang('Log info') :</h3>
+                    @if ($entries->hasPages())
+                        <div class="panel-actions panel-actions-keep">
+                            <div class="badge badge-info float-right">
+                                {{ __('Page :current of :last', ['current' => $entries->currentPage(), 'last' => $entries->lastPage()]) }}
+                            </div>
+                        </div>
+                    @endif
+                </div>
+                <div class="panel-body">
+                    <div class="table-responsive">
+                        <table id="entries" class="table table-striped mb-0">
+                            <thead>
+                            <tr>
+                                <th>ID</th>
+                                <th style="white-space: nowrap;">@lang('ENV')</th>
+                                <th>@lang('Level')</th>
+                                <th>@lang('Time')</th>
+                                <th>@lang('Header')</th>
+                                <th>@lang('Actions')</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            @forelse($entries as $key => $entry)
+                                <?php /** @var  Arcanedev\LogViewer\Entities\LogEntry $entry */ ?>
+                                <tr>
+                                    <td>
+                                        {{ $key+1 }}
+                                    </td>
+                                    <td style="white-space: nowrap;">
+                                        {{ $entry->env }}
+                                    </td>
+                                    <td>
+                                        <span class="badge badge-level-{{ $entry->level }}">
+                                            {!! $entry->level() !!}
+                                        </span>
+                                    </td>
+                                    <td>
+                                        <span class="badge badge-secondary">
+                                            {{ $entry->datetime->format('H:i:s') }}
+                                        </span>
+                                    </td>
+                                    <td>
+                                        {{ $entry->header }}
+                                    </td>
+                                    <td class="text-right">
+                                        @if ($entry->hasStack())
+                                            <a class="btn btn-sm btn-light" role="button" data-toggle="collapse"
+                                               href="#log-stack-{{ $key }}" aria-expanded="false" aria-controls="log-stack-{{ $key }}">
+                                                <i class="fas fa-toggle-on"></i> @lang('Stack')
+                                            </a>
+                                        @endif
+
+                                        @if ($entry->hasContext())
+                                            <a class="btn btn-sm btn-light" role="button" data-toggle="collapse"
+                                               href="#log-context-{{ $key }}" aria-expanded="false" aria-controls="log-context-{{ $key }}">
+                                                <i class="fas fa-toggle-on"></i> @lang('Context')
+                                            </a>
+                                        @endif
+                                    </td>
+                                </tr>
+                                @if ($entry->hasStack() || $entry->hasContext())
+                                    <tr>
+                                        <td colspan="6" class="stack py-0">
+                                            @if ($entry->hasStack())
+                                                <div class="stack-content collapse" id="log-stack-{{ $key }}">
+                                                    {!! $entry->stack() !!}
+                                                </div>
+                                            @endif
+
+                                            @if ($entry->hasContext())
+                                                <div class="stack-content collapse" id="log-context-{{ $key }}">
+                                                    <pre>{{ $entry->context() }}</pre>
+                                                </div>
+                                            @endif
+                                        </td>
+                                    </tr>
+                                @endif
+                            @empty
+                                <tr>
+                                    <td colspan="5" class="text-center">
+                                        <span class="badge badge-secondary">@lang('The list of logs is empty!')</span>
+                                    </td>
+                                </tr>
+                            @endforelse
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+                <div class="panel-footer">
+                    <div class="row">
+                        <div class="col-sm-4">
+                            共 <code>{{$entries->total()}}</code> 条日志
+                        </div>
+                        <div class="col-sm-8">
+                            <nav class="Page navigation float-right">
+                                {{$entries->links()}}
+                            </nav>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection
+
+@section('modals')
+    {{-- DELETE MODAL --}}
+    <div id="deleteLogModal" class="modal fade" aria-hidden="true" aria-labelledby="deleteLogModal"
+         role="dialog" tabindex="-1">
+        <div class="modal-dialog modal-simple modal-center">
+            <form id="deleteLogForm" class="modal-content" action="{{ route('log-viewer::logs.delete') }}" method="POST">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                    <h4 class="modal-title">@lang('Delete log file')</h4>
+                </div>
+                <div class="modal-body">
+                    <input type="hidden" name="_method" value="DELETE">@csrf
+                    <input type="hidden" name="date" value="{{ $log->date }}">
+                    <p>@lang('Are you sure you want to delete this log file: :date ?', ['date' => $log->date])</p>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary mr-auto" data-dismiss="modal">@lang('Cancel')</button>
+                    <button type="submit" class="btn btn-danger" data-loading-text="@lang('Loading')&hellip;">@lang('Delete')</button>
+                </div>
+            </form>
+        </div>
+    </div>
+@endsection
+
+@section('javascript')
+    <script>
+        $(function() {
+            const deleteLogModal = $('div#deleteLogModal'),
+                deleteLogForm = $('form#deleteLogForm'),
+                submitBtn = deleteLogForm.find('button[type=submit]');
+
+            deleteLogForm.on('submit', function(event) {
+                event.preventDefault();
+                submitBtn.button('loading');
+
+                $.ajax({
+                    url: $(this).attr('action'),
+                    type: $(this).attr('method'),
+                    dataType: 'json',
+                    data: $(this).serialize(),
+                    success: function(data) {
+                        submitBtn.button('reset');
+                        if (data.result === 'success') {
+                            deleteLogModal.modal('hide');
+                            location.replace("{{ route('log-viewer::logs.list') }}");
+                        } else {
+                            alert('OOPS ! This is a lack of coffee exception !');
+                        }
+                    },
+                    error: function(xhr, textStatus, errorThrown) {
+                        alert('AJAX ERROR ! Check the console !');
+                        console.error(errorThrown);
+                        submitBtn.button('reset');
+                    },
+                });
+
+                return false;
+            });
+
+            @unless (empty(log_styler()->toHighlight()))
+            @php
+                $htmlHighlight = version_compare(PHP_VERSION, '7.4.0') >= 0
+                    ? join('|', log_styler()->toHighlight())
+                    : join(log_styler()->toHighlight(), '|')
+            @endphp
+
+            $('.stack-content').each(function() {
+                const $this = $(this);
+                const html = $this.html().trim().replace(/({!! $htmlHighlight !!})/gm, '<strong>$1</strong>');
+
+                $this.html(html);
+            });
+            @endunless
+        });
+    </script>
+@endsection

+ 0 - 2
routes/admin.php

@@ -112,6 +112,4 @@ Route::prefix('admin')->name('admin.')->group(function () {
         Route::post('setConfig', 'SystemController@setConfig')->name('system.update'); // 设置某个配置项
         Route::post('sendTestNotification', 'SystemController@sendTestNotification')->name('test.notify'); //推送通知测试
     });
-
-    Route::get('logs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index')->name('log.viewer'); // 系统运行日志
 });

Some files were not shown because too many files changed in this diff