فهرست منبع

Merge pull request #542 from v2board/dev

1.6.1
tokumeikoi 2 سال پیش
والد
کامیت
1a0b09edd2
91فایلهای تغییر یافته به همراه1218 افزوده شده و 1244 حذف شده
  1. 1 0
      app/Console/Commands/CheckTicket.php
  2. 49 0
      app/Console/Commands/ResetLog.php
  3. 0 8
      app/Console/Commands/Test.php
  4. 3 4
      app/Console/Commands/V2boardStatistics.php
  5. 1 0
      app/Console/Kernel.php
  6. 1 1
      app/Http/Controllers/Admin/ConfigController.php
  7. 24 1
      app/Http/Controllers/Admin/PaymentController.php
  8. 2 11
      app/Http/Controllers/Admin/PlanController.php
  9. 0 9
      app/Http/Controllers/Admin/Server/V2rayController.php
  10. 28 1
      app/Http/Controllers/Admin/StatController.php
  11. 64 1
      app/Http/Controllers/Admin/SystemController.php
  12. 1 1
      app/Http/Controllers/Admin/TicketController.php
  13. 1 1
      app/Http/Controllers/Client/Protocols/Clash.php
  14. 5 1
      app/Http/Controllers/Client/Protocols/Stash.php
  15. 3 0
      app/Http/Controllers/Client/Protocols/Surfboard.php
  16. 3 0
      app/Http/Controllers/Client/Protocols/Surge.php
  17. 44 13
      app/Http/Controllers/Guest/TelegramController.php
  18. 67 34
      app/Http/Controllers/Passport/AuthController.php
  19. 0 18
      app/Http/Controllers/Passport/CommController.php
  20. 3 3
      app/Http/Controllers/Server/DeepbworkController.php
  21. 3 3
      app/Http/Controllers/Server/ShadowsocksTidalabController.php
  22. 3 3
      app/Http/Controllers/Server/TrojanTidalabController.php
  23. 3 3
      app/Http/Controllers/Server/VProxyController.php
  24. 1 1
      app/Http/Controllers/Staff/TicketController.php
  25. 5 1
      app/Http/Controllers/User/CommController.php
  26. 1 1
      app/Http/Controllers/User/CouponController.php
  27. 24 17
      app/Http/Controllers/User/InviteController.php
  28. 13 10
      app/Http/Controllers/User/KnowledgeController.php
  29. 20 15
      app/Http/Controllers/User/OrderController.php
  30. 12 3
      app/Http/Controllers/User/PlanController.php
  31. 1 1
      app/Http/Controllers/User/ServerController.php
  32. 1 1
      app/Http/Controllers/User/StatController.php
  33. 1 1
      app/Http/Controllers/User/TelegramController.php
  34. 12 12
      app/Http/Controllers/User/TicketController.php
  35. 18 14
      app/Http/Controllers/User/UserController.php
  36. 11 11
      app/Http/Kernel.php
  37. 22 2
      app/Http/Middleware/Admin.php
  38. 2 2
      app/Http/Middleware/CORS.php
  39. 3 1
      app/Http/Middleware/Client.php
  40. 22 2
      app/Http/Middleware/Staff.php
  41. 15 7
      app/Http/Middleware/User.php
  42. 4 2
      app/Http/Requests/Admin/PlanSave.php
  43. 6 2
      app/Http/Routes/AdminRoute.php
  44. 1 3
      app/Http/Routes/PassportRoute.php
  45. 2 4
      app/Http/Routes/UserRoute.php
  46. 4 4
      app/Jobs/StatServerJob.php
  47. 5 5
      app/Jobs/StatUserJob.php
  48. 4 4
      app/Jobs/TrafficFetchJob.php
  49. 4 1
      app/Payments/StripeAlipay.php
  50. 124 0
      app/Payments/StripeCheckout.php
  51. 4 1
      app/Payments/StripeCredit.php
  52. 4 1
      app/Payments/StripeWepay.php
  53. 4 1
      app/Services/CouponService.php
  54. 41 0
      app/Services/PlanService.php
  55. 16 0
      app/Services/TelegramService.php
  56. 77 37
      app/Services/UserService.php
  57. 2 1
      app/Utils/CacheKey.php
  58. 0 5
      app/Utils/Helper.php
  59. 1 1
      config/app.php
  60. 1 1
      config/horizon.php
  61. 6 5
      database/install.sql
  62. 6 0
      database/update.sql
  63. 0 0
      public/assets/admin/components.async.js
  64. 0 0
      public/assets/admin/components.chunk.css
  65. 0 0
      public/assets/admin/umi.css
  66. 0 0
      public/assets/admin/umi.js
  67. 0 0
      public/assets/admin/vendors.async.js
  68. 0 0
      public/theme/v2board/assets/components.async.js
  69. 0 0
      public/theme/v2board/assets/components.chunk.css
  70. 20 12
      public/theme/v2board/assets/i18n/en-US.js
  71. 29 21
      public/theme/v2board/assets/i18n/ja-JP.js
  72. 31 23
      public/theme/v2board/assets/i18n/ko-KR.js
  73. 46 38
      public/theme/v2board/assets/i18n/vi-VN.js
  74. 14 6
      public/theme/v2board/assets/i18n/zh-CN.js
  75. 15 7
      public/theme/v2board/assets/i18n/zh-TW.js
  76. 0 0
      public/theme/v2board/assets/umi.css
  77. 0 0
      public/theme/v2board/assets/umi.js
  78. 0 7
      public/vendor/horizon/app-dark.css
  79. 0 7
      public/vendor/horizon/app.css
  80. 0 1
      public/vendor/horizon/app.js
  81. BIN
      public/vendor/horizon/img/favicon.png
  82. 0 1
      public/vendor/horizon/img/horizon.svg
  83. 0 806
      public/vendor/horizon/img/sprite.svg
  84. 0 6
      public/vendor/horizon/mix-manifest.json
  85. 5 8
      readme.md
  86. 4 1
      resources/lang/en-US.json
  87. 4 1
      resources/lang/zh-CN.json
  88. 195 0
      resources/views/mail/classic/mailLogin.blade.php
  89. 2 2
      resources/views/mail/classic/notify.blade.php
  90. 43 0
      resources/views/mail/default/mailLogin.blade.php
  91. 1 1
      resources/views/mail/default/notify.blade.php

+ 1 - 0
app/Console/Commands/CheckTicket.php

@@ -41,6 +41,7 @@ class CheckTicket extends Command
         ini_set('memory_limit', -1);
         $tickets = Ticket::where('status', 0)
             ->where('updated_at', '<=', time() - 24 * 3600)
+            ->where('reply_status', 0)
             ->get();
         foreach ($tickets as $ticket) {
             if ($ticket->user_id === $ticket->last_reply_user_id) continue;

+ 49 - 0
app/Console/Commands/ResetLog.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\Plan;
+use App\Models\StatUser;
+use App\Utils\Helper;
+use Illuminate\Console\Command;
+use App\Models\User;
+use Illuminate\Support\Facades\DB;
+
+class ResetLog extends Command
+{
+    protected $builder;
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'reset:log';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '清空日志';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        StatUser::where('record_at', '<', strtotime('-2 month', time()))
+            ->delete();
+    }
+}

+ 0 - 8
app/Console/Commands/Test.php

@@ -2,15 +2,7 @@
 
 namespace App\Console\Commands;
 
-use App\Models\Order;
-use App\Models\User;
-use App\Utils\CacheKey;
-use App\Utils\Helper;
 use Illuminate\Console\Command;
-use Illuminate\Filesystem\Filesystem;
-use Illuminate\Foundation\Console\ConfigCacheCommand;
-use Illuminate\Support\Facades\Cache;
-use Matriphe\Larinfo;
 
 class Test extends Command
 {

+ 3 - 4
app/Console/Commands/V2boardStatistics.php

@@ -53,11 +53,10 @@ class V2boardStatistics extends Command
             ->whereNotIn('status', [0, 2]);
         $orderCount = $orderBuilder->count();
         $orderAmount = $orderBuilder->sum('total_amount');
-        $commissionBuilder = CommissionLog::where('created_at', '>=', $startAt)
-            ->where('created_at', '<', $endAt)
-            ->where('get_amount', '>', 0);
+        $commissionBuilder = Order::where('created_at', '>=', $startAt)
+            ->where('created_at', '<', $endAt);
         $commissionCount = $commissionBuilder->count();
-        $commissionAmount = $commissionBuilder->sum('get_amount');
+        $commissionAmount = $commissionBuilder->sum('actual_commission_balance');
         $data = [
             'order_count' => $orderCount,
             'order_amount' => $orderAmount,

+ 1 - 0
app/Console/Kernel.php

@@ -35,6 +35,7 @@ class Kernel extends ConsoleKernel
         $schedule->command('check:ticket')->everyMinute();
         // reset
         $schedule->command('reset:traffic')->daily();
+        $schedule->command('reset:log')->daily();
         // send
         $schedule->command('send:remindMail')->dailyAt('11:30');
         // horizon metrics

+ 1 - 1
app/Http/Controllers/Admin/ConfigController.php

@@ -39,7 +39,7 @@ class ConfigController extends Controller
     public function testSendMail(Request $request)
     {
         $obj = new SendEmailJob([
-            'email' => $request->session()->get('email'),
+            'email' => $request->user['email'],
             'subject' => 'This is v2board test email',
             'template_name' => 'notify',
             'template_value' => [

+ 24 - 1
app/Http/Controllers/Admin/PaymentController.php

@@ -8,6 +8,7 @@ use App\Utils\Helper;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
 use App\Models\Payment;
+use Illuminate\Support\Facades\DB;
 
 class PaymentController extends Controller
 {
@@ -24,7 +25,7 @@ class PaymentController extends Controller
 
     public function fetch()
     {
-        $payments = Payment::all();
+        $payments = Payment::orderBy('sort', 'ASC')->get();
         foreach ($payments as $k => $v) {
             $notifyUrl = url("/api/v1/guest/payment/notify/{$v->payment}/{$v->uuid}");
             if ($v->notify_domain) {
@@ -107,4 +108,26 @@ class PaymentController extends Controller
             'data' => $payment->delete()
         ]);
     }
+
+
+    public function sort(Request $request)
+    {
+        $request->validate([
+            'ids' => 'required|array'
+        ], [
+            'ids.required' => '参数有误',
+            'ids.array' => '参数有误'
+        ]);
+        DB::beginTransaction();
+        foreach ($request->input('ids') as $k => $v) {
+            if (!Payment::find($v)->update(['sort' => $k + 1])) {
+                DB::rollBack();
+                abort(500, '保存失败');
+            }
+        }
+        DB::commit();
+        return response([
+            'data' => true
+        ]);
+    }
 }

+ 2 - 11
app/Http/Controllers/Admin/PlanController.php

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
 use App\Http\Requests\Admin\PlanSave;
 use App\Http\Requests\Admin\PlanSort;
 use App\Http\Requests\Admin\PlanUpdate;
+use App\Services\PlanService;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
 use App\Models\Plan;
@@ -16,17 +17,7 @@ class PlanController extends Controller
 {
     public function fetch(Request $request)
     {
-        $counts = User::select(
-            DB::raw("plan_id"),
-            DB::raw("count(*) as count")
-        )
-            ->where('plan_id', '!=', NULL)
-            ->where(function ($query) {
-                $query->where('expired_at', '>=', time())
-                    ->orWhere('expired_at', NULL);
-            })
-            ->groupBy("plan_id")
-            ->get();
+        $counts = PlanService::countActiveUsers();
         $plans = Plan::orderBy('sort', 'ASC')->get();
         foreach ($plans as $k => $v) {
             $plans[$k]->count = 0;

+ 0 - 9
app/Http/Controllers/Admin/Server/V2rayController.php

@@ -89,13 +89,4 @@ class V2rayController extends Controller
             'data' => true
         ]);
     }
-
-    public function viewConfig(Request $request)
-    {
-        $serverService = new ServerService();
-        $config = $serverService->getV2RayConfig($request->input('node_id'), 23333);
-        return response([
-            'data' => $config
-        ]);
-    }
 }

+ 28 - 1
app/Http/Controllers/Admin/StatController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
 
 use App\Models\ServerShadowsocks;
 use App\Models\ServerTrojan;
+use App\Models\StatUser;
 use App\Services\ServerService;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
@@ -45,7 +46,15 @@ class StatController extends Controller
                 'last_month_income' => Order::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
                     ->where('created_at', '<', strtotime(date('Y-m-1')))
                     ->whereNotIn('status', [0, 2])
-                    ->sum('total_amount')
+                    ->sum('total_amount'),
+                'commission_month_payout' => Order::where('actual_commission_balance' ,'!=', NULL)
+                    ->where('created_at', '>=', strtotime(date('Y-m-1')))
+                    ->where('created_at', '<', time())
+                    ->sum('actual_commission_balance'),
+                'commission_last_month_payout' => Order::where('actual_commission_balance' ,'!=', NULL)
+                    ->where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
+                    ->where('created_at', '<', strtotime(date('Y-m-1')))
+                    ->sum('actual_commission_balance'),
             ]
         ]);
     }
@@ -123,5 +132,23 @@ class StatController extends Controller
             'data' => $statistics
         ]);
     }
+
+    public function getStatUser(Request $request)
+    {
+        $request->validate([
+            'user_id' => 'required|integer'
+        ]);
+        $current = $request->input('current') ? $request->input('current') : 1;
+        $pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
+        $builder = StatUser::orderBy('record_at', 'DESC')->where('user_id', $request->input('user_id'));
+
+        $total = $builder->count();
+        $records = $builder->forPage($current, $pageSize)
+            ->get();
+        return [
+            'data' => $records,
+            'total' => $total
+        ];
+    }
 }
 

+ 64 - 1
app/Http/Controllers/Admin/SystemController.php

@@ -19,11 +19,16 @@ use App\Models\StatServer;
 use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Http;
+use Laravel\Horizon\Contracts\JobRepository;
 use Laravel\Horizon\Contracts\MasterSupervisorRepository;
+use Laravel\Horizon\Contracts\MetricsRepository;
+use Laravel\Horizon\Contracts\SupervisorRepository;
+use Laravel\Horizon\Contracts\WorkloadRepository;
+use Laravel\Horizon\WaitTimeCalculator;
 
 class SystemController extends Controller
 {
-    public function getStatus()
+    public function getSystemStatus()
     {
         return response([
             'data' => [
@@ -33,6 +38,13 @@ class SystemController extends Controller
         ]);
     }
 
+    public function getQueueWorkload(WorkloadRepository $workload)
+    {
+        return response([
+            'data' => collect($workload->get())->sortBy('name')->values()->toArray()
+        ]);
+    }
+
     protected function getScheduleStatus():bool
     {
         return (time() - 120) < Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null));
@@ -48,5 +60,56 @@ class SystemController extends Controller
             return $master->status === 'paused';
         }) ? false : true;
     }
+
+    public function getQueueStats()
+    {
+        return response([
+            'data' => [
+                'failedJobs' => app(JobRepository::class)->countRecentlyFailed(),
+                'jobsPerMinute' => app(MetricsRepository::class)->jobsProcessedPerMinute(),
+                'pausedMasters' => $this->totalPausedMasters(),
+                'periods' => [
+                    'failedJobs' => config('horizon.trim.recent_failed', config('horizon.trim.failed')),
+                    'recentJobs' => config('horizon.trim.recent'),
+                ],
+                'processes' => $this->totalProcessCount(),
+                'queueWithMaxRuntime' => app(MetricsRepository::class)->queueWithMaximumRuntime(),
+                'queueWithMaxThroughput' => app(MetricsRepository::class)->queueWithMaximumThroughput(),
+                'recentJobs' => app(JobRepository::class)->countRecent(),
+                'status' => $this->getHorizonStatus(),
+                'wait' => collect(app(WaitTimeCalculator::class)->calculate())->take(1),
+            ]
+        ]);
+    }
+
+    /**
+     * Get the total process count across all supervisors.
+     *
+     * @return int
+     */
+    protected function totalProcessCount()
+    {
+        $supervisors = app(SupervisorRepository::class)->all();
+
+        return collect($supervisors)->reduce(function ($carry, $supervisor) {
+            return $carry + collect($supervisor->processes)->sum();
+        }, 0);
+    }
+
+    /**
+     * Get the number of master supervisors that are currently paused.
+     *
+     * @return int
+     */
+    protected function totalPausedMasters()
+    {
+        if (! $masters = app(MasterSupervisorRepository::class)->all()) {
+            return 0;
+        }
+
+        return collect($masters)->filter(function ($master) {
+            return $master->status === 'paused';
+        })->count();
+    }
 }
 

+ 1 - 1
app/Http/Controllers/Admin/TicketController.php

@@ -68,7 +68,7 @@ class TicketController extends Controller
         $ticketService->replyByAdmin(
             $request->input('id'),
             $request->input('message'),
-            $request->session()->get('id')
+            $request->user['id']
         );
         return response([
             'data' => true

+ 1 - 1
app/Http/Controllers/Client/Protocols/Clash.php

@@ -24,6 +24,7 @@ class Clash
         header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
         header('profile-update-interval: 24');
         header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($appName));
+        header("profile-web-page-url:" . config('v2board.app_url'));
         $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
         $customConfig = base_path() . '/resources/rules/custom.clash.yaml';
         if (\File::exists($customConfig)) {
@@ -121,7 +122,6 @@ class Clash
                     $array['ws-opts']['path'] = $wsSettings['path'];
                 if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
                     $array['ws-opts']['headers'] = ['Host' => $wsSettings['headers']['Host']];
-                // TODO: 2022.06.01 remove it
                 if (isset($wsSettings['path']) && !empty($wsSettings['path']))
                     $array['ws-path'] = $wsSettings['path'];
                 if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))

+ 5 - 1
app/Http/Controllers/Client/Protocols/Stash.php

@@ -122,7 +122,6 @@ class Stash
                     $array['ws-opts']['path'] = $wsSettings['path'];
                 if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
                     $array['ws-opts']['headers'] = ['Host' => $wsSettings['headers']['Host']];
-                // TODO: 2022.06.01 remove it
                 if (isset($wsSettings['path']) && !empty($wsSettings['path']))
                     $array['ws-path'] = $wsSettings['path'];
                 if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
@@ -155,6 +154,11 @@ class Stash
         return $array;
     }
 
+    private function isRegex($exp)
+    {
+        return @preg_match($exp, null) !== false;
+    }
+
     private function isMatch($exp, $str)
     {
         try {

+ 3 - 0
app/Http/Controllers/Client/Protocols/Surfboard.php

@@ -21,6 +21,9 @@ class Surfboard
         $servers = $this->servers;
         $user = $this->user;
 
+        $appName = config('v2board.app_name', 'V2Board');
+        header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($appName).".conf");
+
         $proxies = '';
         $proxyGroup = '';
 

+ 3 - 0
app/Http/Controllers/Client/Protocols/Surge.php

@@ -21,6 +21,9 @@ class Surge
         $servers = $this->servers;
         $user = $this->user;
 
+        $appName = config('v2board.app_name', 'V2Board');
+        header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($appName).".conf");
+
         $proxies = '';
         $proxyGroup = '';
 

+ 44 - 13
app/Http/Controllers/Guest/TelegramController.php

@@ -10,25 +10,28 @@ class TelegramController extends Controller
 {
     protected $msg;
     protected $commands = [];
+    protected $telegramService;
 
     public function __construct(Request $request)
     {
         if ($request->input('access_token') !== md5(config('v2board.telegram_bot_token'))) {
             abort(401);
         }
+
+        $this->telegramService = new TelegramService();
     }
 
     public function webhook(Request $request)
     {
-        $this->msg = $this->getMessage($request->input());
-        if (!$this->msg) return;
+        $this->formatMessage($request->input());
+        $this->formatChatJoinRequest($request->input());
         $this->handle();
     }
 
     public function handle()
     {
+        if (!$this->msg) return;
         $msg = $this->msg;
-
         $commandName = explode('@', $msg->command);
 
         // To reduce request, only commands contains @ will get the bot name
@@ -59,34 +62,62 @@ class TelegramController extends Controller
                 }
             }
         } catch (\Exception $e) {
-            $telegramService = new TelegramService();
-            $telegramService->sendMessage($msg->chat_id, $e->getMessage());
+            $this->telegramService->sendMessage($msg->chat_id, $e->getMessage());
         }
     }
 
     public function getBotName()
     {
-        $telegramService = new TelegramService();
-        $response = $telegramService->getMe();
+        $response = $this->telegramService->getMe();
         return $response->result->username;
     }
 
-    private function getMessage(array $data)
+    private function formatMessage(array $data)
     {
-        if (!isset($data['message'])) return false;
+        if (!isset($data['message'])) return;
+        if (!isset($data['message']['text'])) return;
         $obj = new \StdClass();
-        if (!isset($data['message']['text'])) return false;
         $text = explode(' ', $data['message']['text']);
         $obj->command = $text[0];
         $obj->args = array_slice($text, 1);
         $obj->chat_id = $data['message']['chat']['id'];
         $obj->message_id = $data['message']['message_id'];
-        $obj->message_type = !isset($data['message']['reply_to_message']['text']) ? 'message' : 'reply_message';
+        $obj->message_type = 'message';
         $obj->text = $data['message']['text'];
         $obj->is_private = $data['message']['chat']['type'] === 'private';
-        if ($obj->message_type === 'reply_message') {
+        if (isset($data['message']['reply_to_message']['text'])) {
+            $obj->message_type = 'reply_message';
             $obj->reply_text = $data['message']['reply_to_message']['text'];
         }
-        return $obj;
+        $this->msg = $obj;
+    }
+
+    private function formatChatJoinRequest(array $data)
+    {
+        if (!isset($data['chat_join_request'])) return;
+        if (!isset($data['chat_join_request']['from']['id'])) return;
+        if (!isset($data['chat_join_request']['chat']['id'])) return;
+        $user = \App\Models\User::where('telegram_id', $data['chat_join_request']['from']['id'])
+            ->first();
+        if (!$user) {
+            $this->telegramService->declineChatJoinRequest(
+                $data['chat_join_request']['chat']['id'],
+                $data['chat_join_request']['from']['id']
+            );
+            return;
+        }
+        $userService = new \App\Services\UserService();
+        if (!$userService->isAvailable($user)) {
+            $this->telegramService->declineChatJoinRequest(
+                $data['chat_join_request']['chat']['id'],
+                $data['chat_join_request']['from']['id']
+            );
+            return;
+        }
+        $userService = new \App\Services\UserService();
+        $this->telegramService->approveChatJoinRequest(
+            $data['chat_join_request']['chat']['id'],
+            $data['chat_join_request']['from']['id']
+        );
     }
 }

+ 67 - 34
app/Http/Controllers/Passport/AuthController.php

@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
 use App\Http\Requests\Passport\AuthRegister;
 use App\Http\Requests\Passport\AuthForget;
 use App\Http\Requests\Passport\AuthLogin;
+use App\Jobs\SendEmailJob;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Cache;
 use App\Models\Plan;
@@ -18,6 +19,59 @@ use ReCaptcha\ReCaptcha;
 
 class AuthController extends Controller
 {
+    public function loginWithMailLink(Request $request)
+    {
+        if (!(int)config('v2board.login_with_mail_link_enable')) {
+            abort(404);
+        }
+        $params = $request->validate([
+            'email' => 'required|email',
+            'redirect' => 'nullable'
+        ]);
+
+        if (Cache::get(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $params['email']))) {
+            abort(500, __('Sending frequently, please try again later'));
+        }
+
+        $user = User::where('email', $params['email'])->first();
+        if (!$user) {
+            return response([
+                'data' => true
+            ]);
+        }
+
+        $code = Helper::guid();
+        $key = CacheKey::get('TEMP_TOKEN', $code);
+        Cache::put($key, $user->id, 300);
+        Cache::put(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $params['email']), time(), 60);
+
+
+        $redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
+        if (config('v2board.app_url')) {
+            $link = config('v2board.app_url') . $redirect;
+        } else {
+            $link = url($redirect);
+        }
+
+        SendEmailJob::dispatch([
+            'email' => $user->email,
+            'subject' => __('Login to :name', [
+                'name' => config('v2board.app_name', 'V2Board')
+            ]),
+            'template_name' => 'login',
+            'template_value' => [
+                'name' => config('v2board.app_name', 'V2Board'),
+                'link' => $link,
+                'url' => config('v2board.app_url')
+            ]
+        ]);
+
+        return response([
+            'data' => $link
+        ]);
+
+    }
+
     public function register(AuthRegister $request)
     {
         if ((int)config('v2board.register_limit_by_ip_enable', 0)) {
@@ -113,8 +167,7 @@ class AuthController extends Controller
             'token' => $user->token,
             'auth_data' => base64_encode("{$user->email}:{$user->password}")
         ];
-        $request->session()->put('email', $user->email);
-        $request->session()->put('id', $user->id);
+
         $user->last_login_at = time();
         $user->save();
 
@@ -156,16 +209,8 @@ class AuthController extends Controller
             'token' => $user->token,
             'auth_data' => base64_encode("{$user->email}:{$user->password}")
         ];
-        $request->session()->put('email', $user->email);
-        $request->session()->put('id', $user->id);
-        if ($user->is_admin) {
-            $request->session()->put('is_admin', true);
-            $data['is_admin'] = true;
-        }
-        if ($user->is_staff) {
-            $request->session()->put('is_staff', true);
-            $data['is_staff'] = true;
-        }
+
+        if ($user->is_admin) $data['is_admin'] = true;
         return response([
             'data' => $data
         ]);
@@ -196,14 +241,13 @@ class AuthController extends Controller
             if ($user->banned) {
                 abort(500, __('Your account has been suspended'));
             }
-            $request->session()->put('email', $user->email);
-            $request->session()->put('id', $user->id);
-            if ($user->is_admin) {
-                $request->session()->put('is_admin', true);
-            }
+            $data = [
+                'token' => $user->token,
+                'auth_data' => base64_encode("{$user->email}:{$user->password}")
+            ];
             Cache::forget($key);
             return response([
-                'data' => true
+                'data' => $data
             ]);
         }
     }
@@ -225,8 +269,11 @@ class AuthController extends Controller
 
     public function getQuickLoginUrl(Request $request)
     {
-        $authData = explode(':', base64_decode($request->input('auth_data')));
-        if (!isset($authData[0])) abort(403, __('Token error'));
+        $authorization = $request->input('auth_data') ?? $request->header('authorization');
+        if (!$authorization) abort(403, '未登录或登陆已过期');
+
+        $authData = explode(':', base64_decode($authorization));
+        if (!isset($authData[0]) || !isset($authData[1])) abort(403, __('Token error'));
         $user = User::where('email', $authData[0])
             ->where('password', $authData[1])
             ->first();
@@ -248,19 +295,6 @@ class AuthController extends Controller
         ]);
     }
 
-    public function check(Request $request)
-    {
-        $data = [
-            'is_login' => $request->session()->get('id') ? true : false
-        ];
-        if ($request->session()->get('is_admin')) {
-            $data['is_admin'] = true;
-        }
-        return response([
-            'data' => $data
-        ]);
-    }
-
     public function forget(AuthForget $request)
     {
         if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
@@ -281,5 +315,4 @@ class AuthController extends Controller
             'data' => true
         ]);
     }
-
 }

+ 0 - 18
app/Http/Controllers/Passport/CommController.php

@@ -17,24 +17,6 @@ use ReCaptcha\ReCaptcha;
 
 class CommController extends Controller
 {
-    // TODO: remove on 1.5.5
-    public function config()
-    {
-        return response([
-            'data' => [
-                'isEmailVerify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
-                'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0,
-                'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0)
-                    ? $this->getEmailSuffix()
-                    : 0,
-                'isRecaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
-                'recaptchaSiteKey' => config('v2board.recaptcha_site_key'),
-                'appDescription' => config('v2board.app_description'),
-                'appUrl' => config('v2board.app_url')
-            ]
-        ]);
-    }
-
     private function isEmailVerify()
     {
         return response([

+ 3 - 3
app/Http/Controllers/Server/DeepbworkController.php

@@ -82,9 +82,9 @@ class DeepbworkController extends Controller
         Cache::put(CacheKey::get('SERVER_V2RAY_LAST_PUSH_AT', $server->id), time(), 3600);
         $userService = new UserService();
         foreach ($data as $item) {
-            $u = $item['u'] * $server->rate;
-            $d = $item['d'] * $server->rate;
-            $userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess');
+            $u = $item['u'];
+            $d = $item['d'];
+            $userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'vmess');
         }
 
         return response([

+ 3 - 3
app/Http/Controllers/Server/ShadowsocksTidalabController.php

@@ -74,9 +74,9 @@ class ShadowsocksTidalabController extends Controller
         Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
         $userService = new UserService();
         foreach ($data as $item) {
-            $u = $item['u'] * $server->rate;
-            $d = $item['d'] * $server->rate;
-            $userService->trafficFetch($u, $d, $item['user_id'], $server, 'shadowsocks');
+            $u = $item['u'];
+            $d = $item['d'];
+            $userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'shadowsocks');
         }
 
         return response([

+ 3 - 3
app/Http/Controllers/Server/TrojanTidalabController.php

@@ -79,9 +79,9 @@ class TrojanTidalabController extends Controller
         Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
         $userService = new UserService();
         foreach ($data as $item) {
-            $u = $item['u'] * $server->rate;
-            $d = $item['d'] * $server->rate;
-            $userService->trafficFetch($u, $d, $item['user_id'], $server, 'trojan');
+            $u = $item['u'];
+            $d = $item['d'];
+            $userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'trojan');
         }
 
         return response([

+ 3 - 3
app/Http/Controllers/Server/VProxyController.php

@@ -86,9 +86,9 @@ class VProxyController extends Controller
         Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600);
         $userService = new UserService();
         foreach ($data as $item) {
-            $u = $item['u'] * $this->nodeInfo->rate;
-            $d = $item['d'] * $this->nodeInfo->rate;
-            $userService->trafficFetch($u, $d, $item['user_id'], $this->nodeInfo, $this->nodeType);
+            $u = $item['u'];
+            $d = $item['d'];
+            $userService->trafficFetch($u, $d, $item['user_id'], $this->nodeInfo->toArray(), $this->nodeType);
         }
 
         return response([

+ 1 - 1
app/Http/Controllers/Staff/TicketController.php

@@ -57,7 +57,7 @@ class TicketController extends Controller
         $ticketService->replyByAdmin(
             $request->input('id'),
             $request->input('message'),
-            $request->session()->get('id')
+            $request->user['id']
         );
         return response([
             'data' => true

+ 5 - 1
app/Http/Controllers/User/CommController.php

@@ -19,7 +19,11 @@ class CommController extends Controller
                 'withdraw_methods' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
                 'withdraw_close' => (int)config('v2board.withdraw_close_enable', 0),
                 'currency' => config('v2board.currency', 'CNY'),
-                'currency_symbol' => config('v2board.currency_symbol', '¥')
+                'currency_symbol' => config('v2board.currency_symbol', '¥'),
+                'commission_distribution_enable' => (int)config('v2board.commission_distribution_enable', 0),
+                'commission_distribution_l1' => config('v2board.commission_distribution_l1'),
+                'commission_distribution_l2' => config('v2board.commission_distribution_l2'),
+                'commission_distribution_l3' => config('v2board.commission_distribution_l3')
             ]
         ]);
     }

+ 1 - 1
app/Http/Controllers/User/CouponController.php

@@ -16,7 +16,7 @@ class CouponController extends Controller
         }
         $couponService = new CouponService($request->input('code'));
         $couponService->setPlanId($request->input('plan_id'));
-        $couponService->setUserId($request->session()->get('id'));
+        $couponService->setUserId($request->user['id']);
         $couponService->check();
         return response([
             'data' => $couponService->getCoupon()

+ 24 - 17
app/Http/Controllers/User/InviteController.php

@@ -14,11 +14,11 @@ class InviteController extends Controller
 {
     public function save(Request $request)
     {
-        if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) {
+        if (InviteCode::where('user_id', $request->user['id'])->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) {
             abort(500, __('The maximum number of creations has been reached'));
         }
         $inviteCode = new InviteCode();
-        $inviteCode->user_id = $request->session()->get('id');
+        $inviteCode->user_id = $request->user['id'];
         $inviteCode->code = Helper::randomChar(8);
         return response([
             'data' => $inviteCode->save()
@@ -27,42 +27,49 @@ class InviteController extends Controller
 
     public function details(Request $request)
     {
+        $current = $request->input('current') ? $request->input('current') : 1;
+        $pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
+        $builder = CommissionLog::where('invite_user_id', $request->user['id'])
+            ->where('get_amount', '>', 0)
+            ->select([
+                'id',
+                'trade_no',
+                'order_amount',
+                'get_amount',
+                'created_at'
+            ])
+            ->orderBy('created_at', 'DESC');
+        $total = $builder->count();
+        $details = $builder->forPage($current, $pageSize)
+            ->get();
         return response([
-            'data' => CommissionLog::where('invite_user_id', $request->session()->get('id'))
-                ->where('get_amount', '>', 0)
-                ->select([
-                    'id',
-                    'trade_no',
-                    'order_amount',
-                    'get_amount',
-                    'created_at'
-                ])
-                ->get()
+            'data' => $details,
+            'total' => $total
         ]);
     }
 
     public function fetch(Request $request)
     {
-        $codes = InviteCode::where('user_id', $request->session()->get('id'))
+        $codes = InviteCode::where('user_id', $request->user['id'])
             ->where('status', 0)
             ->get();
         $commission_rate = config('v2board.invite_commission', 10);
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         if ($user->commission_rate) {
             $commission_rate = $user->commission_rate;
         }
         $stat = [
             //已注册用户数
-            (int)User::where('invite_user_id', $request->session()->get('id'))->count(),
+            (int)User::where('invite_user_id', $request->user['id'])->count(),
             //有效的佣金
             (int)Order::where('status', 3)
                 ->where('commission_status', 2)
-                ->where('invite_user_id', $request->session()->get('id'))
+                ->where('invite_user_id', $request->user['id'])
                 ->sum('commission_balance'),
             //确认中的佣金
             (int)Order::where('status', 3)
                 ->where('commission_status', 0)
-                ->where('invite_user_id', $request->session()->get('id'))
+                ->where('invite_user_id', $request->user['id'])
                 ->sum('commission_balance'),
             //佣金比例
             (int)$commission_rate,

+ 13 - 10
app/Http/Controllers/User/KnowledgeController.php

@@ -19,14 +19,9 @@ class KnowledgeController extends Controller
                 ->first()
                 ->toArray();
             if (!$knowledge) abort(500, __('Article does not exist'));
-            $user = User::find($request->session()->get('id'));
+            $user = User::find($request->user['id']);
             $userService = new UserService();
-            if ($userService->isAvailable($user)) {
-                $appleId = config('v2board.apple_id');
-                $appleIdPassword = config('v2board.apple_id_password');
-            } else {
-                $appleId = __('No active subscription. Unable to use our provided Apple ID');
-                $appleIdPassword = __('No active subscription. Unable to use our provided Apple ID');
+            if (!$userService->isAvailable($user)) {
                 $this->formatAccessData($knowledge['body']);
             }
             $subscribeUrl = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
@@ -46,11 +41,19 @@ class KnowledgeController extends Controller
                 'data' => $knowledge
             ]);
         }
-        $knowledges = Knowledge::select(['id', 'category', 'title', 'updated_at'])
+        $builder = Knowledge::select(['id', 'category', 'title', 'updated_at'])
             ->where('language', $request->input('language'))
             ->where('show', 1)
-            ->orderBy('sort', 'ASC')
-            ->get()
+            ->orderBy('sort', 'ASC');
+        $keyword = $request->input('keyword');
+        if ($keyword) {
+            $builder = $builder->where(function ($query) use ($keyword) {
+                $query->where('title', 'LIKE', "%{$keyword}%")
+                    ->orWhere('body', 'LIKE', "%{$keyword}%");
+            });
+        }
+
+        $knowledges = $builder->get()
             ->groupBy('category');
         return response([
             'data' => $knowledges

+ 20 - 15
app/Http/Controllers/User/OrderController.php

@@ -9,6 +9,7 @@ use App\Models\Payment;
 use App\Services\CouponService;
 use App\Services\OrderService;
 use App\Services\PaymentService;
+use App\Services\PlanService;
 use App\Services\UserService;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Cache;
@@ -28,7 +29,7 @@ class OrderController extends Controller
 {
     public function fetch(Request $request)
     {
-        $model = Order::where('user_id', $request->session()->get('id'))
+        $model = Order::where('user_id', $request->user['id'])
             ->orderBy('created_at', 'DESC');
         if ($request->input('status') !== null) {
             $model->where('status', $request->input('status'));
@@ -49,7 +50,7 @@ class OrderController extends Controller
 
     public function detail(Request $request)
     {
-        $order = Order::where('user_id', $request->session()->get('id'))
+        $order = Order::where('user_id', $request->user['id'])
             ->where('trade_no', $request->input('trade_no'))
             ->first();
         if (!$order) {
@@ -71,28 +72,30 @@ class OrderController extends Controller
     public function save(OrderSave $request)
     {
         $userService = new UserService();
-        if ($userService->isNotCompleteOrderByUserId($request->session()->get('id'))) {
+        if ($userService->isNotCompleteOrderByUserId($request->user['id'])) {
             abort(500, __('You have an unpaid or pending order, please try again later or cancel it'));
         }
 
-        $plan = Plan::find($request->input('plan_id'));
-        $user = User::find($request->session()->get('id'));
+        $planService = new PlanService($request->input('plan_id'));
+
+        $plan = $planService->plan;
+        $user = User::find($request->user['id']);
 
         if (!$plan) {
             abort(500, __('Subscription plan does not exist'));
         }
 
+        if (!$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
+            abort(500, __('Current product is sold out'));
+        }
+
         if ($plan[$request->input('period')] === NULL) {
             abort(500, __('This payment period cannot be purchased, please choose another period'));
         }
 
         if ($request->input('period') === 'reset_price') {
-            if (!$user->plan_id) {
+            if (!$userService->isAvailable($user) || $plan->id !== $user->plan_id) {
                 abort(500, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package'));
-            } else {
-                if ($user->plan_id !== $plan->id) {
-                    abort(500, __('This subscription reset package does not apply to your subscription'));
-                }
             }
         }
 
@@ -114,7 +117,7 @@ class OrderController extends Controller
         DB::beginTransaction();
         $order = new Order();
         $orderService = new OrderService($order);
-        $order->user_id = $request->session()->get('id');
+        $order->user_id = $request->user['id'];
         $order->plan_id = $plan->id;
         $order->period = $request->input('period');
         $order->trade_no = Helper::generateOrderNo();
@@ -170,7 +173,7 @@ class OrderController extends Controller
         $tradeNo = $request->input('trade_no');
         $method = $request->input('method');
         $order = Order::where('trade_no', $tradeNo)
-            ->where('user_id', $request->session()->get('id'))
+            ->where('user_id', $request->user['id'])
             ->where('status', 0)
             ->first();
         if (!$order) {
@@ -209,7 +212,7 @@ class OrderController extends Controller
     {
         $tradeNo = $request->input('trade_no');
         $order = Order::where('trade_no', $tradeNo)
-            ->where('user_id', $request->session()->get('id'))
+            ->where('user_id', $request->user['id'])
             ->first();
         if (!$order) {
             abort(500, __('Order does not exist'));
@@ -229,7 +232,9 @@ class OrderController extends Controller
             'handling_fee_fixed',
             'handling_fee_percent'
         ])
-            ->where('enable', 1)->get();
+            ->where('enable', 1)
+            ->orderBy('sort', 'ASC')
+            ->get();
 
         return response([
             'data' => $methods
@@ -242,7 +247,7 @@ class OrderController extends Controller
             abort(500, __('Invalid parameter'));
         }
         $order = Order::where('trade_no', $request->input('trade_no'))
-            ->where('user_id', $request->session()->get('id'))
+            ->where('user_id', $request->user['id'])
             ->first();
         if (!$order) {
             abort(500, __('Order does not exist'));

+ 12 - 3
app/Http/Controllers/User/PlanController.php

@@ -4,14 +4,16 @@ namespace App\Http\Controllers\User;
 
 use App\Http\Controllers\Controller;
 use App\Models\User;
+use App\Services\PlanService;
 use Illuminate\Http\Request;
 use App\Models\Plan;
+use Illuminate\Support\Facades\DB;
 
 class PlanController extends Controller
 {
     public function fetch(Request $request)
     {
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         if ($request->input('id')) {
             $plan = Plan::where('id', $request->input('id'))->first();
             if (!$plan) {
@@ -24,11 +26,18 @@ class PlanController extends Controller
                 'data' => $plan
             ]);
         }
-        $plan = Plan::where('show', 1)
+
+        $counts = PlanService::countActiveUsers();
+        $plans = Plan::where('show', 1)
             ->orderBy('sort', 'ASC')
             ->get();
+        foreach ($plans as $k => $v) {
+            if ($plans[$k]->capacity_limit === NULL) continue;
+            if (!isset($counts[$plans[$k]->id])) continue;
+            $plans[$k]->capacity_limit = $plans[$k]->capacity_limit - $counts[$plans[$k]->id]->count;
+        }
         return response([
-            'data' => $plan
+            'data' => $plans
         ]);
     }
 }

+ 1 - 1
app/Http/Controllers/User/ServerController.php

@@ -19,7 +19,7 @@ class ServerController extends Controller
 {
     public function fetch(Request $request)
     {
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         $servers = [];
         $userService = new UserService();
         if ($userService->isAvailable($user)) {

+ 1 - 1
app/Http/Controllers/User/StatController.php

@@ -18,7 +18,7 @@ class StatController extends Controller
             'user_id',
             'server_rate'
         ])
-            ->where('user_id', $request->session()->get('id'))
+            ->where('user_id', $request->user['id'])
             ->where('record_at', '>=', strtotime(date('Y-m-1')))
             ->orderBy('record_at', 'DESC');
         return response([

+ 1 - 1
app/Http/Controllers/User/TelegramController.php

@@ -22,6 +22,6 @@ class TelegramController extends Controller
 
     public function unbind(Request $request)
     {
-        $user = User::where('user_id', $request->session()->get('id'))->first();
+        $user = User::where('user_id', $request->user['id'])->first();
     }
 }

+ 12 - 12
app/Http/Controllers/User/TicketController.php

@@ -21,7 +21,7 @@ class TicketController extends Controller
     {
         if ($request->input('id')) {
             $ticket = Ticket::where('id', $request->input('id'))
-                ->where('user_id', $request->session()->get('id'))
+                ->where('user_id', $request->user['id'])
                 ->first();
             if (!$ticket) {
                 abort(500, __('Ticket does not exist'));
@@ -38,7 +38,7 @@ class TicketController extends Controller
                 'data' => $ticket
             ]);
         }
-        $ticket = Ticket::where('user_id', $request->session()->get('id'))
+        $ticket = Ticket::where('user_id', $request->user['id'])
             ->orderBy('created_at', 'DESC')
             ->get();
         return response([
@@ -49,21 +49,21 @@ class TicketController extends Controller
     public function save(TicketSave $request)
     {
         DB::beginTransaction();
-        if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->lockForUpdate()->count()) {
+        if ((int)Ticket::where('status', 0)->where('user_id', $request->user['id'])->lockForUpdate()->count()) {
             abort(500, __('There are other unresolved tickets'));
         }
         $ticket = Ticket::create(array_merge($request->only([
             'subject',
             'level'
         ]), [
-            'user_id' => $request->session()->get('id')
+            'user_id' => $request->user['id']
         ]));
         if (!$ticket) {
             DB::rollback();
             abort(500, __('Failed to open ticket'));
         }
         $ticketMessage = TicketMessage::create([
-            'user_id' => $request->session()->get('id'),
+            'user_id' => $request->user['id'],
             'ticket_id' => $ticket->id,
             'message' => $request->input('message')
         ]);
@@ -87,7 +87,7 @@ class TicketController extends Controller
             abort(500, __('Message cannot be empty'));
         }
         $ticket = Ticket::where('id', $request->input('id'))
-            ->where('user_id', $request->session()->get('id'))
+            ->where('user_id', $request->user['id'])
             ->first();
         if (!$ticket) {
             abort(500, __('Ticket does not exist'));
@@ -95,14 +95,14 @@ class TicketController extends Controller
         if ($ticket->status) {
             abort(500, __('The ticket is closed and cannot be replied'));
         }
-        if ($request->session()->get('id') == $this->getLastMessage($ticket->id)->user_id) {
+        if ($request->user['id'] == $this->getLastMessage($ticket->id)->user_id) {
             abort(500, __('Please wait for the technical enginneer to reply'));
         }
         $ticketService = new TicketService();
         if (!$ticketService->reply(
             $ticket,
             $request->input('message'),
-            $request->session()->get('id')
+            $request->user['id']
         )) {
             abort(500, __('Ticket reply failed'));
         }
@@ -119,7 +119,7 @@ class TicketController extends Controller
             abort(500, __('Invalid parameter'));
         }
         $ticket = Ticket::where('id', $request->input('id'))
-            ->where('user_id', $request->session()->get('id'))
+            ->where('user_id', $request->user['id'])
             ->first();
         if (!$ticket) {
             abort(500, __('Ticket does not exist'));
@@ -154,7 +154,7 @@ class TicketController extends Controller
         )) {
             abort(500, __('Unsupported withdrawal method'));
         }
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         $limit = config('v2board.commission_withdraw_limit', 100);
         if ($limit > ($user->commission_balance / 100)) {
             abort(500, __('The current required minimum withdrawal commission is :limit', ['limit' => $limit]));
@@ -164,7 +164,7 @@ class TicketController extends Controller
         $ticket = Ticket::create([
             'subject' => $subject,
             'level' => 2,
-            'user_id' => $request->session()->get('id')
+            'user_id' => $request->user['id']
         ]);
         if (!$ticket) {
             DB::rollback();
@@ -175,7 +175,7 @@ class TicketController extends Controller
             __('Withdrawal account') . ":" . $request->input('withdraw_account')
         );
         $ticketMessage = TicketMessage::create([
-            'user_id' => $request->session()->get('id'),
+            'user_id' => $request->user['id'],
             'ticket_id' => $ticket->id,
             'message' => $message
         ]);

+ 18 - 14
app/Http/Controllers/User/UserController.php

@@ -18,17 +18,22 @@ use Illuminate\Support\Facades\Cache;
 
 class UserController extends Controller
 {
-    public function logout(Request $request)
+    public function checkLogin(Request $request)
     {
-        $request->session()->flush();
+        $data = [
+            'is_login' => $request->user['id'] ? true : false
+        ];
+        if ($request->user['is_admin']) {
+            $data['is_admin'] = true;
+        }
         return response([
-            'data' => true
+            'data' => $data
         ]);
     }
 
     public function changePassword(UserChangePassword $request)
     {
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         if (!$user) {
             abort(500, __('The user does not exist'));
         }
@@ -46,7 +51,6 @@ class UserController extends Controller
         if (!$user->save()) {
             abort(500, __('Save failed'));
         }
-        $request->session()->flush();
         return response([
             'data' => true
         ]);
@@ -54,7 +58,7 @@ class UserController extends Controller
 
     public function info(Request $request)
     {
-        $user = User::where('id', $request->session()->get('id'))
+        $user = User::where('id', $request->user['id'])
             ->select([
                 'email',
                 'transfer_enable',
@@ -86,12 +90,12 @@ class UserController extends Controller
     {
         $stat = [
             Order::where('status', 0)
-                ->where('user_id', $request->session()->get('id'))
+                ->where('user_id', $request->user['id'])
                 ->count(),
             Ticket::where('status', 0)
-                ->where('user_id', $request->session()->get('id'))
+                ->where('user_id', $request->user['id'])
                 ->count(),
-            User::where('invite_user_id', $request->session()->get('id'))
+            User::where('invite_user_id', $request->user['id'])
                 ->count()
         ];
         return response([
@@ -101,7 +105,7 @@ class UserController extends Controller
 
     public function getSubscribe(Request $request)
     {
-        $user = User::where('id', $request->session()->get('id'))
+        $user = User::where('id', $request->user['id'])
             ->select([
                 'plan_id',
                 'token',
@@ -131,7 +135,7 @@ class UserController extends Controller
 
     public function resetSecurity(Request $request)
     {
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         if (!$user) {
             abort(500, __('The user does not exist'));
         }
@@ -152,7 +156,7 @@ class UserController extends Controller
             'remind_traffic'
         ]);
 
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         if (!$user) {
             abort(500, __('The user does not exist'));
         }
@@ -169,7 +173,7 @@ class UserController extends Controller
 
     public function transfer(UserTransfer $request)
     {
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         if (!$user) {
             abort(500, __('The user does not exist'));
         }
@@ -188,7 +192,7 @@ class UserController extends Controller
 
     public function getQuickLoginUrl(Request $request)
     {
-        $user = User::find($request->session()->get('id'));
+        $user = User::find($request->user['id']);
         if (!$user) {
             abort(500, __('The user does not exist'));
         }

+ 11 - 11
app/Http/Kernel.php

@@ -2,6 +2,7 @@
 
 namespace App\Http;
 
+use Fruitcake\Cors\HandleCors;
 use Illuminate\Foundation\Http\Kernel as HttpKernel;
 
 class Kernel extends HttpKernel
@@ -14,6 +15,7 @@ class Kernel extends HttpKernel
      * @var array
      */
     protected $middleware = [
+        \App\Http\Middleware\CORS::class,
         \App\Http\Middleware\TrustProxies::class,
         \App\Http\Middleware\CheckForMaintenanceMode::class,
         \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
@@ -28,22 +30,20 @@ class Kernel extends HttpKernel
      */
     protected $middlewareGroups = [
         'web' => [
-            \App\Http\Middleware\EncryptCookies::class,
-            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
-            \Illuminate\Session\Middleware\StartSession::class,
+//            \App\Http\Middleware\EncryptCookies::class,
+//            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+//            \Illuminate\Session\Middleware\StartSession::class,
             // \Illuminate\Session\Middleware\AuthenticateSession::class,
-            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
-            \App\Http\Middleware\VerifyCsrfToken::class,
-            \Illuminate\Routing\Middleware\SubstituteBindings::class,
-            \App\Http\Middleware\CORS::class,
+//            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+//            \App\Http\Middleware\VerifyCsrfToken::class,
+//            \Illuminate\Routing\Middleware\SubstituteBindings::class,
         ],
 
         'api' => [
-            \App\Http\Middleware\EncryptCookies::class,
-            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
-            \Illuminate\Session\Middleware\StartSession::class,
+//            \App\Http\Middleware\EncryptCookies::class,
+//            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+//            \Illuminate\Session\Middleware\StartSession::class,
             \App\Http\Middleware\ForceJson::class,
-            \App\Http\Middleware\CORS::class,
             \App\Http\Middleware\Language::class,
             'bindings',
         ],

+ 22 - 2
app/Http/Middleware/Admin.php

@@ -3,6 +3,7 @@
 namespace App\Http\Middleware;
 
 use Closure;
+use Illuminate\Support\Facades\Cache;
 
 class Admin
 {
@@ -15,9 +16,28 @@ class Admin
      */
     public function handle($request, Closure $next)
     {
-        if (!$request->session()->get('is_admin')) {
-            abort(403, '权限不足');
+        $authorization = $request->input('auth_data') ?? $request->header('authorization');
+        if (!$authorization) abort(403, '未登录或登陆已过期');
+
+        $authData = explode(':', base64_decode($authorization));
+        if (!Cache::has($authorization)) {
+            if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
+            $user = \App\Models\User::where('password', $authData[1])
+                ->where('email', $authData[0])
+                ->select([
+                    'id',
+                    'email',
+                    'is_admin',
+                    'is_staff'
+                ])
+                ->first();
+            if (!$user) abort(403, '鉴权失败,请重新登入');
+            if (!$user->is_admin) abort(403, '鉴权失败,请重新登入');
+            Cache::put($authorization, $user->toArray(), 3600);
         }
+        $request->merge([
+            'user' => Cache::get($authorization)
+        ]);
         return $next($request);
     }
 }

+ 2 - 2
app/Http/Middleware/CORS.php

@@ -17,8 +17,8 @@ class CORS
         }
         $response = $next($request);
         $response->header('Access-Control-Allow-Origin', trim($origin, '/'));
-        $response->header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
-        $response->header('Access-Control-Allow-Headers', 'Content-Type,X-Requested-With');
+        $response->header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS,HEAD');
+        $response->header('Access-Control-Allow-Headers', 'Origin,Content-Type,Accept,Authorization,X-Request-With');
         $response->header('Access-Control-Allow-Credentials', 'true');
         $response->header('Access-Control-Max-Age', 10080);
 

+ 3 - 1
app/Http/Middleware/Client.php

@@ -26,7 +26,9 @@ class Client
         if (!$user) {
             abort(403, 'token is error');
         }
-        $request->user = $user;
+        $request->merge([
+            'user' => $user
+        ]);
         return $next($request);
     }
 }

+ 22 - 2
app/Http/Middleware/Staff.php

@@ -3,6 +3,7 @@
 namespace App\Http\Middleware;
 
 use Closure;
+use Illuminate\Support\Facades\Cache;
 
 class Staff
 {
@@ -15,9 +16,28 @@ class Staff
      */
     public function handle($request, Closure $next)
     {
-        if (!$request->session()->get('is_staff')) {
-            abort(403, '权限不足');
+        $authorization = $request->input('auth_data') ?? $request->header('authorization');
+        if (!$authorization) abort(403, '未登录或登陆已过期');
+
+        $authData = explode(':', base64_decode($authorization));
+        if (!Cache::has($authorization)) {
+            if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
+            $user = \App\Models\User::where('password', $authData[1])
+                ->where('email', $authData[0])
+                ->select([
+                    'id',
+                    'email',
+                    'is_admin',
+                    'is_staff'
+                ])
+                ->first();
+            if (!$user) abort(403, '鉴权失败,请重新登入');
+            if (!$user->is_staff) abort(403, '鉴权失败,请重新登入');
+            Cache::put($authorization, $user->toArray(), 3600);
         }
+        $request->merge([
+            'user' => Cache::get($authorization)
+        ]);
         return $next($request);
     }
 }

+ 15 - 7
app/Http/Middleware/User.php

@@ -3,6 +3,7 @@
 namespace App\Http\Middleware;
 
 use Closure;
+use Illuminate\Support\Facades\Cache;
 
 class User
 {
@@ -16,19 +17,26 @@ class User
     public function handle($request, Closure $next)
     {
         $authorization = $request->input('auth_data') ?? $request->header('authorization');
-        if ($authorization) {
-            $authData = explode(':', base64_decode($authorization));
+        if (!$authorization) abort(403, '未登录或登陆已过期');
+
+        $authData = explode(':', base64_decode($authorization));
+        if (!Cache::has($authorization)) {
             if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
             $user = \App\Models\User::where('password', $authData[1])
                 ->where('email', $authData[0])
+                ->select([
+                    'id',
+                    'email',
+                    'is_admin',
+                    'is_staff'
+                ])
                 ->first();
             if (!$user) abort(403, '鉴权失败,请重新登入');
-            $request->session()->put('email', $user->email);
-            $request->session()->put('id', $user->id);
-        }
-        if (!$request->session()->get('id')) {
-            abort(403, '未登录或登陆已过期');
+            Cache::put($authorization, $user->toArray(), 3600);
         }
+        $request->merge([
+            'user' => Cache::get($authorization)
+        ]);
         return $next($request);
     }
 }

+ 4 - 2
app/Http/Requests/Admin/PlanSave.php

@@ -26,7 +26,8 @@ class PlanSave extends FormRequest
             'three_year_price' => 'nullable|integer',
             'onetime_price' => 'nullable|integer',
             'reset_price' => 'nullable|integer',
-            'reset_traffic_method' => 'nullable|integer|in:0,1,2,3,4'
+            'reset_traffic_method' => 'nullable|integer|in:0,1,2,3,4',
+            'capacity_limit' => 'nullable|integer'
         ];
     }
 
@@ -47,7 +48,8 @@ class PlanSave extends FormRequest
             'onetime_price.integer' => '一次性金额有误',
             'reset_price.integer' => '流量重置包金额有误',
             'reset_traffic_method.integer' => '流量重置方式格式有误',
-            'reset_traffic_method.in' => '流量重置方式格式有误'
+            'reset_traffic_method.in' => '流量重置方式格式有误',
+            'capacity_limit.integer' => '容纳用户量限制格式有误'
         ];
     }
 }

+ 6 - 2
app/Http/Routes/AdminRoute.php

@@ -50,7 +50,6 @@ class AdminRoute
                 $router->post('update', 'Admin\\Server\\V2rayController@update');
                 $router->post('copy', 'Admin\\Server\\V2rayController@copy');
                 $router->post('sort', 'Admin\\Server\\V2rayController@sort');
-                $router->post('viewConfig', 'Admin\\Server\\V2rayController@viewConfig');
             });
             $router->group([
                 'prefix' => 'server/shadowsocks'
@@ -83,6 +82,7 @@ class AdminRoute
             $router->get ('/stat/getOverride', 'Admin\\StatController@getOverride');
             $router->get ('/stat/getServerLastRank', 'Admin\\StatController@getServerLastRank');
             $router->get ('/stat/getOrder', 'Admin\\StatController@getOrder');
+            $router->get ('/stat/getStatUser', 'Admin\\StatController@getStatUser');
             // Notice
             $router->get ('/notice/fetch', 'Admin\\NoticeController@fetch');
             $router->post('/notice/save', 'Admin\\NoticeController@save');
@@ -112,8 +112,12 @@ class AdminRoute
             $router->post('/payment/save', 'Admin\\PaymentController@save');
             $router->post('/payment/drop', 'Admin\\PaymentController@drop');
             $router->post('/payment/show', 'Admin\\PaymentController@show');
+            $router->post('/payment/sort', 'Admin\\PaymentController@sort');
             // System
-            $router->get ('/system/getStatus', 'Admin\\SystemController@getStatus');
+            $router->get ('/system/getSystemStatus', 'Admin\\SystemController@getSystemStatus');
+            $router->get ('/system/getQueueStats', 'Admin\\SystemController@getQueueStats');
+            $router->get ('/system/getQueueWorkload', 'Admin\\SystemController@getQueueWorkload');
+            $router->get ('/system/getQueueMasters', '\\Laravel\\Horizon\\Http\\Controllers\\MasterSupervisorController@index');
             // Theme
             $router->get ('/theme/getThemes', 'Admin\\ThemeController@getThemes');
             $router->post('/theme/saveThemeConfig', 'Admin\\ThemeController@saveThemeConfig');

+ 1 - 3
app/Http/Routes/PassportRoute.php

@@ -14,12 +14,10 @@ class PassportRoute
             $router->post('/auth/register', 'Passport\\AuthController@register');
             $router->post('/auth/login', 'Passport\\AuthController@login');
             $router->get ('/auth/token2Login', 'Passport\\AuthController@token2Login');
-            $router->get ('/auth/check', 'Passport\\AuthController@check');
             $router->post('/auth/forget', 'Passport\\AuthController@forget');
-            $router->post('/auth/getTempToken', 'Passport\\AuthController@getTempToken');
             $router->post('/auth/getQuickLoginUrl', 'Passport\\AuthController@getQuickLoginUrl');
+            $router->post('/auth/loginWithMailLink', 'Passport\\AuthController@loginWithMailLink');
             // Comm
-            $router->get ('/comm/config', 'Passport\\CommController@config');
             $router->post('/comm/sendEmailVerify', 'Passport\\CommController@sendEmailVerify');
             $router->post('/comm/pv', 'Passport\\CommController@pv');
         });

+ 2 - 4
app/Http/Routes/UserRoute.php

@@ -13,21 +13,19 @@ class UserRoute
         ], function ($router) {
             // User
             $router->get ('/resetSecurity', 'User\\UserController@resetSecurity');
-            $router->get ('/logout', 'User\\UserController@logout');
             $router->get ('/info', 'User\\UserController@info');
             $router->post('/changePassword', 'User\\UserController@changePassword');
             $router->post('/update', 'User\\UserController@update');
             $router->get ('/getSubscribe', 'User\\UserController@getSubscribe');
             $router->get ('/getStat', 'User\\UserController@getStat');
+            $router->get ('/checkLogin', 'User\\UserController@checkLogin');
             $router->post('/transfer', 'User\\UserController@transfer');
             $router->post('/getQuickLoginUrl', 'User\\UserController@getQuickLoginUrl');
             // Order
             $router->post('/order/save', 'User\\OrderController@save');
             $router->post('/order/checkout', 'User\\OrderController@checkout');
             $router->get ('/order/check', 'User\\OrderController@check');
-            // TODO: 1.5.6 remove
-            $router->get ('/order/details', 'User\\OrderController@detail');
-            // TODO: 1.5.6 remove
+            $router->get ('/order/details', 'User\\OrderController@detail');                                            // TODO: 1.7.0 remove
             $router->get ('/order/detail', 'User\\OrderController@detail');
             $router->get ('/order/fetch', 'User\\OrderController@fetch');
             $router->get ('/order/getPaymentMethod', 'User\\OrderController@getPaymentMethod');

+ 4 - 4
app/Jobs/StatServerJob.php

@@ -48,10 +48,10 @@ class StatServerJob implements ShouldQueue
             //
         }
 
-        $data = StatServer::where('record_at', $recordAt)
-            ->where('server_id', $this->server->id)
+        $data = StatServer::lockForUpdate()
+            ->where('record_at', $recordAt)
+            ->where('server_id', $this->server['id'])
             ->where('server_type', $this->protocol)
-            ->lockForUpdate()
             ->first();
         if ($data) {
             try {
@@ -64,7 +64,7 @@ class StatServerJob implements ShouldQueue
             }
         } else {
             if (!StatServer::create([
-                'server_id' => $this->server->id,
+                'server_id' => $this->server['id'],
                 'server_type' => $this->protocol,
                 'u' => $this->u,
                 'd' => $this->d,

+ 5 - 5
app/Jobs/StatUserJob.php

@@ -28,7 +28,7 @@ class StatUserJob implements ShouldQueue
      *
      * @return void
      */
-    public function __construct($u, $d, $userId, $server, $protocol, $recordType = 'd')
+    public function __construct($u, $d, $userId, array $server, $protocol, $recordType = 'd')
     {
         $this->onQueue('stat');
         $this->u = $u;
@@ -52,14 +52,14 @@ class StatUserJob implements ShouldQueue
         }
 
         $data = StatUser::where('record_at', $recordAt)
-            ->where('server_rate', $this->server->rate)
+            ->where('server_rate', $this->server['rate'])
             ->where('user_id', $this->userId)
             ->first();
         if ($data) {
             try {
                 $data->update([
-                    'u' => $data['u'] + $this->u,
-                    'd' => $data['d'] + $this->d
+                    'u' => $data['u'] + ($this->u * $this->server['rate']),
+                    'd' => $data['d'] + ($this->d * $this->server['rate'])
                 ]);
             } catch (\Exception $e) {
                 abort(500, '用户统计数据更新失败');
@@ -67,7 +67,7 @@ class StatUserJob implements ShouldQueue
         } else {
             if (!StatUser::create([
                 'user_id' => $this->userId,
-                'server_rate' => $this->server->rate,
+                'server_rate' => $this->server['rate'],
                 'u' => $this->u,
                 'd' => $this->d,
                 'record_type' => $this->recordType,

+ 4 - 4
app/Jobs/TrafficFetchJob.php

@@ -27,7 +27,7 @@ class TrafficFetchJob implements ShouldQueue
      *
      * @return void
      */
-    public function __construct($u, $d, $userId, $server, $protocol)
+    public function __construct($u, $d, $userId, array $server, $protocol)
     {
         $this->onQueue('traffic_fetch');
         $this->u = $u;
@@ -46,10 +46,10 @@ class TrafficFetchJob implements ShouldQueue
     {
         $user = User::lockForUpdate()->find($this->userId);
         if (!$user) return;
-        
+
         $user->t = time();
-        $user->u = $user->u + $this->u;
-        $user->d = $user->d + $this->d;
+        $user->u = $user->u + ($this->u * $this->server['rate']);
+        $user->d = $user->d + ($this->d * $this->server['rate']);
         if (!$user->save()) throw new \Exception('流量更新失败');
         $mailService = new MailService();
         $mailService->remindTraffic($user);

+ 4 - 1
app/Payments/StripeAlipay.php

@@ -91,11 +91,14 @@ class StripeAlipay {
             case 'charge.succeeded':
                 $object = $event->data->object;
                 if ($object->status === 'succeeded') {
+                    if (!isset($object->metadata->out_trade_no) && !isset($object->source->metadata)) {
+                        die('order error');
+                    }
                     $metaData = isset($object->metadata->out_trade_no) ? $object->metadata : $object->source->metadata;
                     $tradeNo = $metaData->out_trade_no;
                     return [
                         'trade_no' => $tradeNo,
-                        'callback_no' => $object->balance_transaction
+                        'callback_no' => $object->id
                     ];
                 }
                 break;

+ 124 - 0
app/Payments/StripeCheckout.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace App\Payments;
+
+use Stripe\Stripe;
+use Stripe\Checkout\Session;
+
+class StripeCheckout {
+    public function __construct($config)
+    {
+        $this->config = $config;
+    }
+
+    public function form()
+    {
+        return [
+            'currency' => [
+                'label' => '货币单位',
+                'description' => '',
+                'type' => 'input',
+            ],
+            'stripe_sk_live' => [
+                'label' => 'SK_LIVE',
+                'description' => 'API 密钥',
+                'type' => 'input',
+            ],
+            'stripe_pk_live' => [
+                'label' => 'PK_LIVE',
+                'description' => 'API 公钥',
+                'type' => 'input',
+            ],
+            'stripe_webhook_key' => [
+                'label' => 'WebHook 密钥签名',
+                'description' => '',
+                'type' => 'input',
+            ]
+        ];
+    }
+
+    public function pay($order)
+    {
+        $currency = $this->config['currency'];
+        $exchange = $this->exchange('CNY', strtoupper($currency));
+        if (!$exchange) {
+            abort(500, __('Currency conversion has timed out, please try again later'));
+        }
+
+        $params = [
+            'success_url' => $order['return_url'],
+            'cancel_url' => $order['return_url'],
+            'client_reference_id' => $order['trade_no'],
+            'line_items' => [
+                [
+                    'price_data' => [
+                        'currency' => $currency,
+                        'product_data' => [
+                            'name' => $order['trade_no']
+                        ],
+                        'unit_amount' => floor($order['total_amount'] * $exchange)
+                    ],
+                    'quantity' => 1
+                ]
+            ],
+            'mode' => 'payment'
+            // 'customer_email' => $user['email'] not support
+
+        ];
+
+        Stripe::setApiKey($this->config['stripe_sk_live']);
+        try {
+            $session = Session::create($params);
+        } catch (\Exception $e) {
+            info($e);
+            abort(500, "Failed to create order. Error: {$e->getMessage}");
+        }
+        return [
+            'type' => 1, // 0:qrcode 1:url
+            'data' => $session->url
+        ];
+    }
+
+    public function notify($params)
+    {
+        \Stripe\Stripe::setApiKey($this->config['stripe_sk_live']);
+        try {
+            $event = \Stripe\Webhook::constructEvent(
+                file_get_contents('php://input'),
+                $_SERVER['HTTP_STRIPE_SIGNATURE'],
+                $this->config['stripe_webhook_key']
+            );
+        } catch (\Stripe\Error\SignatureVerification $e) {
+            abort(400);
+        }
+
+        switch ($event->type) {
+            case 'checkout.session.completed':
+                $object = $event->data->object;
+                if ($object->payment_status === 'paid') {
+                    return [
+                        'trade_no' => $object->client_reference_id,
+                        'callback_no' => $object->payment_intent
+                    ];
+                }
+                break;
+            case 'checkout.session.async_payment_succeeded':
+                $object = $event->data->object;
+                return [
+                    'trade_no' => $object->client_reference_id,
+                    'callback_no' => $object->payment_intent
+                ];
+                break;
+            default:
+                abort(500, 'event is not support');
+        }
+        die('success');
+    }
+
+    private function exchange($from, $to)
+    {
+        $result = file_get_contents('https://api.exchangerate.host/latest?symbols=' . $to . '&base=' . $from);
+        $result = json_decode($result, true);
+        return $result['rates'][$to];
+    }
+}

+ 4 - 1
app/Payments/StripeCredit.php

@@ -98,11 +98,14 @@ class StripeCredit {
             case 'charge.succeeded':
                 $object = $event->data->object;
                 if ($object->status === 'succeeded') {
+                    if (!isset($object->metadata->out_trade_no) && !isset($object->source->metadata)) {
+                        die('order error');
+                    }
                     $metaData = isset($object->metadata->out_trade_no) ? $object->metadata : $object->source->metadata;
                     $tradeNo = $metaData->out_trade_no;
                     return [
                         'trade_no' => $tradeNo,
-                        'callback_no' => $object->balance_transaction
+                        'callback_no' => $object->id
                     ];
                 }
                 break;

+ 4 - 1
app/Payments/StripeWepay.php

@@ -91,11 +91,14 @@ class StripeWepay {
             case 'charge.succeeded':
                 $object = $event->data->object;
                 if ($object->status === 'succeeded') {
+                    if (!isset($object->metadata->out_trade_no) && !isset($object->source->metadata)) {
+                        die('order error');
+                    }
                     $metaData = isset($object->metadata->out_trade_no) ? $object->metadata : $object->source->metadata;
                     $tradeNo = $metaData->out_trade_no;
                     return [
                         'trade_no' => $tradeNo,
-                        'callback_no' => $object->balance_transaction
+                        'callback_no' => $object->id
                     ];
                 }
                 break;

+ 4 - 1
app/Services/CouponService.php

@@ -15,7 +15,9 @@ class CouponService
 
     public function __construct($code)
     {
-        $this->coupon = Coupon::where('code', $code)->first();
+        $this->coupon = Coupon::where('code', $code)
+            ->lockForUpdate()
+            ->first();
     }
 
     public function use(Order $order):bool
@@ -36,6 +38,7 @@ class CouponService
             $order->discount_amount = $order->total_amount;
         }
         if ($this->coupon->limit_use !== NULL) {
+            if ($this->coupon->limit_use <= 0) return false;
             $this->coupon->limit_use = $this->coupon->limit_use - 1;
             if (!$this->coupon->save()) {
                 return false;

+ 41 - 0
app/Services/PlanService.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\Plan;
+use App\Models\User;
+use Illuminate\Support\Facades\DB;
+
+class PlanService
+{
+    public $plan;
+
+    public function __construct(int $planId)
+    {
+        $this->plan = Plan::lockForUpdate()->find($planId);
+    }
+
+    public function haveCapacity(): bool
+    {
+        if ($this->plan->capacity_limit === NULL) return true;
+        $count = self::countActiveUsers();
+        $count = $count[$this->plan->id]['count'] ?? 0;
+        return ($this->plan->capacity_limit - $count) > 0;
+    }
+
+    public static function countActiveUsers()
+    {
+        return User::select(
+            DB::raw("plan_id"),
+            DB::raw("count(*) as count")
+        )
+            ->where('plan_id', '!=', NULL)
+            ->where(function ($query) {
+                $query->where('expired_at', '>=', time())
+                    ->orWhere('expired_at', NULL);
+            })
+            ->groupBy("plan_id")
+            ->get()
+            ->keyBy('plan_id');
+    }
+}

+ 16 - 0
app/Services/TelegramService.php

@@ -26,6 +26,22 @@ class TelegramService {
         ]);
     }
 
+    public function approveChatJoinRequest(int $chatId, int $userId)
+    {
+        $this->request('approveChatJoinRequest', [
+            'chat_id' => $chatId,
+            'user_id' => $userId
+        ]);
+    }
+
+    public function declineChatJoinRequest(int $chatId, int $userId)
+    {
+        $this->request('declineChatJoinRequest', [
+            'chat_id' => $chatId,
+            'user_id' => $userId
+        ]);
+    }
+
     public function getMe()
     {
         return $this->request('getMe');

+ 77 - 37
app/Services/UserService.php

@@ -8,6 +8,7 @@ use App\Jobs\StatUserJob;
 use App\Jobs\TrafficFetchJob;
 use App\Models\InviteCode;
 use App\Models\Order;
+use App\Models\Plan;
 use App\Models\ServerV2ray;
 use App\Models\Ticket;
 use App\Models\User;
@@ -15,48 +16,87 @@ use Illuminate\Support\Facades\DB;
 
 class UserService
 {
-    public function getResetDay(User $user)
+    private function calcResetDayByMonthFirstDay()
     {
-        if ($user->expired_at <= time() || $user->expired_at === NULL) return null;
-        // if reset method is not reset
-        if (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 2) return null;
+        $today = date('d');
+        $lastDay = date('d', strtotime('last day of +0 months'));
+        return $lastDay - $today;
+    }
 
-        if ((int)config('v2board.reset_traffic_method') === 0 ||
-            (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 0))
-        {
-            $day = date('d', $user->expired_at);
-            $today = date('d');
-            $lastDay = date('d', strtotime('last day of +0 months'));
+    private function calcResetDayByExpireDay(int $expiredAt)
+    {
+        $day = date('d', $expiredAt);
+        $today = date('d');
+        $lastDay = date('d', strtotime('last day of +0 months'));
+        if ((int)$day >= (int)$today && (int)$day >= (int)$lastDay) {
             return $lastDay - $today;
         }
-        if ((int)config('v2board.reset_traffic_method') === 1 ||
-            (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 1))
-        {
-            $day = date('d', $user->expired_at);
-            $today = date('d');
-            $lastDay = date('d', strtotime('last day of +0 months'));
-            if ((int)$day >= (int)$today && (int)$day >= (int)$lastDay) {
-                return $lastDay - $today;
-            }
-            if ((int)$day >= (int)$today) {
-                return $day - $today;
-            } else {
-                return $lastDay - $today + $day;
-            }
+        if ((int)$day >= (int)$today) {
+            return $day - $today;
+        } else {
+            return $lastDay - $today + $day;
         }
-        if ((int)config('v2board.reset_traffic_method') === 3 ||
-            (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 3))
-        {
-            $nextYear = strtotime(date("Y-01-01", strtotime('+1 year')));
-            return (int)(($nextYear - time()) / 86400);
+    }
+
+    private function calcResetDayByYearFirstDay(): int
+    {
+        $nextYear = strtotime(date("Y-01-01", strtotime('+1 year')));
+        return (int)(($nextYear - time()) / 86400);
+    }
+
+    private function calcResetDayByYearExpiredAt(int $expiredAt): int
+    {
+        $md = date('m-d', $expiredAt);
+        $nowYear = strtotime(date("Y-{$md}"));
+        $nextYear = strtotime('+1 year', $nowYear);
+        return (int)(($nextYear - time()) / 86400);
+    }
+
+    public function getResetDay(User $user)
+    {
+        if (!isset($user->plan)) {
+            $user->plan = Plan::find($user->plan_id);
         }
-        if ((int)config('v2board.reset_traffic_method') === 4 ||
-            (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 4))
-        {
-            $md = date('m-d', $user->expired_at);
-            $nowYear = strtotime(date("Y-{$md}"));
-            $nextYear = strtotime('+1 year', $nowYear);
-            return (int)(($nextYear - time()) / 86400);
+        if ($user->expired_at <= time() || $user->expired_at === NULL) return null;
+        // if reset method is not reset
+        if ($user->plan->reset_traffic_method === 2) return null;
+        switch (true) {
+            case ($user->plan->reset_traffic_method === NULL): {
+                $resetTrafficMethod = config('v2board.reset_traffic_method', 0);
+                switch ((int)$resetTrafficMethod) {
+                    // month first day
+                    case 0:
+                        return $this->calcResetDayByMonthFirstDay();
+                    // expire day
+                    case 1:
+                        return $this->calcResetDayByExpireDay($user->expired_at);
+                    // no action
+                    case 2:
+                        return null;
+                    // year first day
+                    case 3:
+                        return $this->calcResetDayByYearFirstDay();
+                    // year expire day
+                    case 4:
+                        return $this->calcResetDayByYearExpiredAt($user->expired_at);
+                }
+                break;
+            }
+            case ($user->plan->reset_traffic_method === 0): {
+                return $this->calcResetDayByMonthFirstDay();
+            }
+            case ($user->plan->reset_traffic_method === 1): {
+                return $this->calcResetDayByExpireDay($user->expired_at);
+            }
+            case ($user->plan->reset_traffic_method === 2): {
+                return null;
+            }
+            case ($user->plan->reset_traffic_method === 3): {
+                return $this->calcResetDayByYearFirstDay();
+            }
+            case ($user->plan->reset_traffic_method === 4): {
+                return $this->calcResetDayByYearExpiredAt($user->expired_at);
+            }
         }
         return null;
     }
@@ -130,7 +170,7 @@ class UserService
         return true;
     }
 
-    public function trafficFetch(int $u, int $d, int $userId, object $server, string $protocol)
+    public function trafficFetch(int $u, int $d, int $userId, array $server, string $protocol)
     {
         TrafficFetchJob::dispatch($u, $d, $userId, $server, $protocol);
         StatServerJob::dispatch($u, $d, $server, $protocol, 'd');

+ 2 - 1
app/Utils/CacheKey.php

@@ -19,7 +19,8 @@ class CacheKey
         'TEMP_TOKEN' => '临时令牌',
         'LAST_SEND_EMAIL_REMIND_TRAFFIC' => '最后发送流量邮件提醒',
         'SCHEDULE_LAST_CHECK_AT' => '计划任务最后检查时间',
-        'REGISTER_IP_RATE_LIMIT' => '注册频率限制'
+        'REGISTER_IP_RATE_LIMIT' => '注册频率限制',
+        'LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP' => '最后一次发送登入链接时间'
     ];
 
     public static function get(string $key, $uniqueValue)

+ 0 - 5
app/Utils/Helper.php

@@ -2,11 +2,6 @@
 
 namespace App\Utils;
 
-use App\Models\ServerV2ray;
-use App\Models\ServerShadowsocks;
-use App\Models\ServerTrojan;
-use App\Models\User;
-
 class Helper
 {
     public static function guid($format = false)

+ 1 - 1
config/app.php

@@ -237,5 +237,5 @@ return [
     | The only modification by laravel config
     |
     */
-    'version' => '1.6.0'
+    'version' => '1.6.1.1665920414108'
 ];

+ 1 - 1
config/horizon.php

@@ -74,7 +74,7 @@ return [
     |
     */
 
-    'middleware' => ['web', 'admin'],
+    'middleware' => ['admin'],
 
     /*
     |--------------------------------------------------------------------------

+ 6 - 5
database/install.sql

@@ -84,10 +84,10 @@ CREATE TABLE `v2_knowledge` (
 DROP TABLE IF EXISTS `v2_mail_log`;
 CREATE TABLE `v2_mail_log` (
                                `id` int(11) NOT NULL AUTO_INCREMENT,
-                               `email` varchar(64) CHARACTER SET utf8 NOT NULL,
-                               `subject` varchar(255) CHARACTER SET utf8 NOT NULL,
-                               `template_name` varchar(255) CHARACTER SET utf8 NOT NULL,
-                               `error` text CHARACTER SET utf8,
+                               `email` varchar(64) NOT NULL,
+                               `subject` varchar(255) NOT NULL,
+                               `template_name` varchar(255) NOT NULL,
+                               `error` text,
                                `created_at` int(11) NOT NULL,
                                `updated_at` int(11) NOT NULL,
                                PRIMARY KEY (`id`)
@@ -176,6 +176,7 @@ CREATE TABLE `v2_plan` (
                            `onetime_price` int(11) DEFAULT NULL,
                            `reset_price` int(11) DEFAULT NULL,
                            `reset_traffic_method` tinyint(1) DEFAULT NULL,
+                           `capacity_limit` int(11) DEFAULT NULL,
                            `created_at` int(11) NOT NULL,
                            `updated_at` int(11) NOT NULL,
                            PRIMARY KEY (`id`)
@@ -378,4 +379,4 @@ CREATE TABLE `v2_user` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
--- 2022-06-10 17:12:02
+-- 2022-07-07 18:23:17

+ 6 - 0
database/update.sql

@@ -586,3 +586,9 @@ ALTER TABLE `v2_mail_log`
     CHANGE `subject` `subject` varchar(255) NOT NULL AFTER `email`,
     CHANGE `template_name` `template_name` varchar(255) NOT NULL AFTER `subject`,
     CHANGE `error` `error` text NULL AFTER `template_name`;
+
+ALTER TABLE `v2_plan`
+    ADD `inventory_limit` int(11) NULL AFTER `reset_traffic_method`;
+
+ALTER TABLE `v2_plan`
+    CHANGE `inventory_limit` `capacity_limit` int(11) NULL AFTER `reset_traffic_method`;

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/assets/admin/components.async.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/assets/admin/components.chunk.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/assets/admin/umi.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/assets/admin/umi.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/assets/admin/vendors.async.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/theme/v2board/assets/components.async.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/theme/v2board/assets/components.chunk.css


+ 20 - 12
public/theme/v2board/assets/i18n/en-US.js

@@ -80,11 +80,11 @@ window.settings.i18n['en-US'] = {
   '总计': 'Total',
   '结账': 'Checkout',
   '等待支付中': 'Waiting for payment',
-  '开通中': 'Pending',
+  '开通中': 'Pending Active',
   '订单系统正在进行处理,请稍等1-3分钟。': 'Order system is being processed, please wait 1 to 3 minutes.',
   '已取消': 'Canceled',
   '订单由于超时支付已被取消。': 'The order has been canceled due to overtime payment.',
-  '已完成': 'Success',
+  '已完成': 'Completed',
   '订单已支付并开通。': 'The order has been paid and the service is activated.',
   '选择订阅': 'Select a Subscription',
   '立即订阅': 'Subscribe now',
@@ -126,7 +126,7 @@ window.settings.i18n['en-US'] = {
   '邮箱': 'Email',
   '邮箱验证码': 'Email verification code',
   '发送': 'Send',
-  '邀请码': 'Invitation code',
+  '邀请码': 'Invitation Code',
   '邀请码(选填)': 'Invitation code (Optional)',
   '注册': 'Register',
   '返回登入': 'Back to Login',
@@ -146,11 +146,11 @@ window.settings.i18n['en-US'] = {
   '已用 {used} / 总计 {total}': '{used} Used / Total {total}',
   '重置订阅信息': 'Reset Subscription',
   '没有可用节点,如果您未订阅或已过期请': 'No access points are available. If you have not subscribed or the subscription has expired, please',
-  '订阅': 'Subscribe',
-  '确定要重置当月流量?': 'Are you sure to reset your usage for the current month?',
+  '订阅': 'Subscription',
+  '确定重置当前已用流量?': 'Are you sure to reset your current data usage?',
   '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': 'Click "Confirm" and you will be redirected to the payment page. The system will empty your current month\'s usage after your purchase.',
   '确定': 'Confirm',
-  '确定要重置订阅信息?': 'Are you sure to reset your subscription?',
+  '确定要重置订阅信息?': 'Do you want to reset subscription?',
   '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': 'In case of your account information or subscription leak, this option is for reset. After resetting your UUID and subscription will change, you need to re-subscribe.',
   '重置成功': 'Reset successfully',
   '低': 'Low',
@@ -166,7 +166,7 @@ window.settings.i18n['en-US'] = {
   '查看': 'View',
   '关闭': 'Cancel',
   '新的工单': 'My Tickets',
-  '新的工单': 'New Ticket',
+  '新的工单': 'My Tickets',
   '确认': 'Confirm',
   '主题': 'Subject',
   '请输入工单主题': 'Please enter a subject',
@@ -207,12 +207,10 @@ window.settings.i18n['en-US'] = {
   '使用文档': 'Knowledge Base',
   '最后更新: {date}': 'Last Updated: {date}',
   '复制成功': 'Copied successfully',
-  '我的订阅': 'My Subscription',
   '还有没支付的订单': 'There are still unpaid orders',
   '立即支付': 'Pay Now',
   '条工单正在处理中': 'tickets are in process',
   '立即查看': 'View Now',
-  '购买订阅': 'Purchase Subscription',
   '使用文档': 'Knowledge Base',
   '我的订单': 'My Orders',
   '流量明细': 'Transfer Data Details',
@@ -233,7 +231,11 @@ window.settings.i18n['en-US'] = {
   '余额支付': 'Balance payment',
   '我的工单': 'My Tickets',
   '工单历史': 'Ticket History',
-  '{reset_day} 日后重置流量': 'after {reset_day} days reset usage',
+  '已用流量将在 {reset_day} 日后重置': 'Used data will reset after {reset_day} days',
+  '已用流量已在今日重置': 'Data usage has been reset today',
+  '重置已用流量': 'Reset used data',
+  '查看节点状态': 'View Access Point status',
+  '当前已使用流量达{rate}%': 'Currently used data up to {rate}%',
   '节点名称': 'Access Point Name',
   '于 {date} 到期,距离到期还有 {day} 天。': 'Will expire on {date}, {day} days before expiration, ',
   'Telegram 讨论组': 'Telegram Discussion Group',
@@ -263,7 +265,13 @@ window.settings.i18n['en-US'] = {
   '选择最适合你的计划': 'Choose the right plan for you',
   '全部': 'All',
   '按周期': 'By Cycle',
-  '一次性': 'One Time',
   '遇到问题': 'I have a problem',
-  '遇到问题可以通过工单与我们沟通': 'If you have any problems, you can contact us via ticket'
+  '遇到问题可以通过工单与我们沟通': 'If you have any problems, you can contact us via ticket',
+  '按流量': 'Pay As You Go',
+  '搜索文档': 'Search Documents',
+  '技术支持': 'Technical Support',
+  '当前剩余佣金': 'Current commission remaining',
+  '三级分销比例': 'Three-level Distribution Ratio',
+  '累计获得佣金': 'Cumulative commission earned',
+  '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': 'The users you invite to re-invite users will be divided according to the order amount multiplied by the distribution level.'
 };

+ 29 - 21
public/theme/v2board/assets/i18n/ja-JP.js

@@ -6,7 +6,7 @@ window.settings.i18n['ja-JP'] = {
   '年付': '年間プラン',
   '两年付': '2年プラン',
   '三年付': '3年プラン',
-  '一次性': '使い切りプラン',
+  '一次性': '一括払い',
   '重置流量包': '使用済みデータをリセット',
   '待支付': 'お支払い待ち',
   '开通中': '処理中',
@@ -80,11 +80,11 @@ window.settings.i18n['ja-JP'] = {
   '总计': '合計金額',
   '结账': 'チェックアウト',
   '等待支付中': 'お支払い待ち',
-  '开通中': 'プラン処理中',
+  '开通中': '処理中',
   '订单系统正在进行处理,请稍等1-3分钟。': 'システム処理中です、しばらくお待ちください',
-  '已取消': 'キャンセル',
+  '已取消': 'キャンセル済み',
   '订单由于超时支付已被取消。': 'ご注文はキャンセルされました',
-  '已完成': '完了しました',
+  '已完成': '済み',
   '订单已支付并开通。': 'お支払いが完了しました、プランはご利用可能です',
   '选择订阅': 'プランをお選びください',
   '立即订阅': '今すぐ購入',
@@ -95,7 +95,7 @@ window.settings.i18n['ja-JP'] = {
   '验证': '確定',
   '订单总额': 'ご注文の合計金額',
   '下单': 'チェックアウト',
-  '总计': '合計',
+  '总计': '合計金額',
   '变更订阅会导致当前订阅被新订阅覆盖,请注意。': 'プランを変更なされます場合は、既存のプランが新規プランによって上書きされます、ご注意下さい',
   '该订阅无法续费': '該当プランは継続利用できません',
   '选择其他订阅': 'その他のプランを選択',
@@ -140,19 +140,19 @@ window.settings.i18n['ja-JP'] = {
   '使用的流量将乘以倍率进行扣除': '通信量は該当レートに基き計算されます',
   '更多操作': 'アクション',
   '复制成功': 'クリップボードにコピーされました',
-  '复制链接': 'リンクをコピー',
+  '复制链接': 'URLをコピー',
   '该订阅长期有效': '時間制限なし',
   '已过期': '期限切れ',
   '已用 {used} / 总计 {total}': '使用済み {used} / 合計 {total}',
   '重置订阅信息': 'サブスクリプションURLの変更',
   '没有可用节点,如果您未订阅或已过期请': 'ご利用可能なサーバーがありません,プランの期限切れまたは購入なされていない場合は',
-  '订阅': '購入',
-  '确定要重置当月流量?': '当月分の使用済みデータ通信量をリセットしますか?',
+  '订阅': 'サブスクリプションプラン',
+  '确定重置当前已用流量?': '确定重置当前已用流量?',
   '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '「確定」をクリックし次のページへ移動,お支払い後に当月分のデータ通信量は即時リセットされます',
   '确定': '確定',
   '确定要重置订阅信息?': 'サブスクリプションURLやUUIDをご変更なされますか?',
   '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': 'サブスクリプションのURL及び情報が外部に漏れた場合にご操作ください。操作後はUUIDやURLが変更され、再度サブスクリプションのインポートが必要になります',
-  '重置成功': '変更完了しました',
+  '重置成功': '変更完了',
   '低': '低',
   '中': '中',
   '高': '高',
@@ -185,7 +185,7 @@ window.settings.i18n['ja-JP'] = {
   '一键订阅': 'ワンクリックインポート',
   '复制订阅': 'サブスクリプションのURLをコピー',
   '推广佣金划转至余额': 'コミッションを残高へ振替',
-  '确认': '確定',
+  '确认': '送信',
   '划转后的余额仅用于{title}消费使用': '振替済みの残高は{title}でのみご利用可能です',
   '当前推广佣金余额': '現在のコミッション金額',
   '划转金额': '振替金額',
@@ -207,17 +207,15 @@ window.settings.i18n['ja-JP'] = {
   '使用文档': 'ナレッジベース',
   '最后更新: {date}': '最終更新日: {date}',
   '复制成功': 'クリップボードにコピーされました',
-  '我的订阅': 'ご利用中のプラン',
   '还有没支付的订单': '未払いのご注文があります',
   '立即支付': 'チェックアウト',
   '条工单正在处理中': '件のお問い合わせ',
   '立即查看': '閲覧',
-  '购买订阅': 'プランの購入',
   '使用文档': 'ナレッジベース',
   '我的订单': '注文履歴',
   '流量明细': 'データ通信明細',
   '配置订阅': 'プランの内訳',
-  '我的邀请': '招待メニュー',
+  '我的邀请': '招待リスト',
   '节点状态': 'サーバーステータス',
   '复制成功': 'クリップボードにコピーされました',
   '商品信息': 'プラン詳細',
@@ -233,7 +231,11 @@ window.settings.i18n['ja-JP'] = {
   '余额支付': '残高ご利用分',
   '我的工单': 'お問い合わせ',
   '工单历史': 'お問い合わせ履歴',
-  '{reset_day} 日后重置流量': '{reset_day} 日後にカウントリセット',
+  '已用流量将在 {reset_day} 日后重置': '已用流量将在 {reset_day} 日后重置',
+  '已用流量已在今日重置': '已用流量已在今日重置',
+  '重置已用流量': '重置已用流量',
+  '查看节点状态': '查看节点状态',
+  '当前已使用流量达{rate}%': '当前已使用流量达{rate}%',
   '节点名称': 'サーバー名',
   '于 {date} 到期,距离到期还有 {day} 天。': 'ご利用期限は {date} まで,期限まであと {day} 日',
   'Telegram 讨论组': 'Telegramグループ',
@@ -243,7 +245,7 @@ window.settings.i18n['ja-JP'] = {
   '流量明细仅保留近月数据以供查询。': 'データ通信明細は当月分のみ表示されます',
   '扣费倍率': '適応レート',
   '支付手续费': 'お支払い手数料',
-  '续费订阅': '续费订阅',
+  '续费订阅': '購読更新',
   '学习如何使用': '学习如何使用',
   '快速将节点导入对应客户端进行使用': '快速将节点导入对应客户端进行使用',
   '对您当前的订阅进行续费': '对您当前的订阅进行续费',
@@ -252,9 +254,9 @@ window.settings.i18n['ja-JP'] = {
   '不会使用,查看使用教程': '不会使用,查看使用教程',
   '使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅',
   '扫描二维码订阅': '扫描二维码订阅',
-  '续费': '续费',
-  '购买': '购买',
-  '查看教程': '查看教程',
+  '续费': '更新',
+  '购买': '購入',
+  '查看教程': 'チュートリアルを表示',
   '注意': '注意',
   '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?',
   '确定取消': '确定取消',
@@ -262,8 +264,14 @@ window.settings.i18n['ja-JP'] = {
   '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?',
   '选择最适合你的计划': '选择最适合你的计划',
   '全部': '全部',
-  '按周期': '按周期',
-  '一次性': '一次性',
+  '按周期': '周期別',
   '遇到问题': '遇到问题',
-  '遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通'
+  '遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通',
+  '按流量': '按流量',
+  '搜索文档': '搜索文档',
+  '技术支持': '技术支持',
+  '当前剩余佣金': '当前剩余佣金',
+  '三级分销比例': '三级分销比例',
+  '累计获得佣金': '累计获得佣金',
+  '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。'
 };

+ 31 - 23
public/theme/v2board/assets/i18n/ko-KR.js

@@ -33,7 +33,7 @@ window.settings.i18n['ko-KR'] = {
   '使用文档': '사용 설명서',
   '绑定Telegram获取更多服务': '텔레그램에 아직 연결되지 않았습니다',
   '点击这里进行绑定': '텔레그램에 연결되도록 여기를 눌러주세요',
-  '公告': '공고',
+  '公告': '발표',
   '总览': '개요',
   '该订阅长期有效': '구독은 무제한으로 유효합니다',
   '已过期': '만료',
@@ -54,7 +54,7 @@ window.settings.i18n['ko-KR'] = {
   '佣金将会在确认后会到达你的佣金账户。': '수수료는 검토 후 수수료 계정에서 확인할 수 있습니다',
   '邀请码管理': '초청 코드 관리',
   '生成邀请码': '초청 코드 생성하기',
-  '佣金发放记录': '佣金发放记录',
+  '佣金发放记录': '수수료 지불 기록',
   '复制成功': '성공적으로 복사 됨',
   '密码': '비밀번호',
   '登入': '로그인',
@@ -76,15 +76,15 @@ window.settings.i18n['ko-KR'] = {
   '支付方式': '지불 방식',
   '填写信用卡支付信息': '신용카드 지불 정보를 적으세요',
   '您的信用卡信息只会被用作当次扣款,系统并不会保存,这是我们认为最安全的。': '현재 거래를 확인하는 데 사용하는 귀하의 신용 카드 정보, 신용 카드 번호 및 기타 세부 정보를 수집하지 않습니다.',
-  '订单总额': '전체 주문',
+  '订单总额': '전체주문',
   '总计': '전체',
-  '结账': '점검',
+  '结账': '결제하기',
   '等待支付中': '결제 대기 중',
-  '开通中': '개통 중',
+  '开通中': '보류 활성화',
   '订单系统正在进行处理,请稍等1-3分钟。': '주문 시스템이 처리 중입니다. 1-3분 정도 기다려 주십시오.',
   '已取消': '취소 됨',
   '订单由于超时支付已被取消。': '결제 시간 초과로 인해 주문이 취소되었습니다.',
-  '已完成': '성공',
+  '已完成': '완료',
   '订单已支付并开通。': '주문이 결제되고 개통되었습니다.',
   '选择订阅': '구독 선택하기',
   '立即订阅': '지금 구독하기',
@@ -93,7 +93,7 @@ window.settings.i18n['ko-KR'] = {
   '付款周期': '지불 기간',
   '有优惠券?': '쿠폰을 가지고 있나요?',
   '验证': '확인',
-  '订单总额': '전체 주문',
+  '订单总额': '전체주문',
   '下单': '주문',
   '总计': '전체',
   '变更订阅会导致当前订阅被新订阅覆盖,请注意。': '주의하십시오. 구독을 변경하면 현재 구독을 덮어씁니다',
@@ -129,7 +129,7 @@ window.settings.i18n['ko-KR'] = {
   '邀请码': '초청 코드',
   '邀请码(选填)': '초청 코드(선택 사항)',
   '注册': '등록하기',
-  '返回登入': '로그인으로 돌아가기',
+  '返回登入': '로그인 다시하기',
   '我已阅读并同意 <a target="_blank" href="{url}">服务条款</a>': '을 읽었으며 이에 동의합니다 <a target="_blank" href="{url}">서비스 약관</a>',
   '请同意服务条款': '서비스 약관에 동의해주세요',
   '名称': '이름',
@@ -139,19 +139,19 @@ window.settings.i18n['ko-KR'] = {
   '倍率': '요금',
   '使用的流量将乘以倍率进行扣除': '사용된 전송 데이터에 전송 데이터 요금을 뺀 값을 곱합니다.',
   '更多操作': '설정',
-  '复制成功': '성공적으로 복사',
+  '复制成功': '성공적으로 복사',
   '复制链接': '링크 복사',
-  '该订阅长期有效': '나의 구독',
-  '已过期': '구독 주문하기',
-  '已用 {used} / 总计 {total}': '{used} 이용량/{total} 전체',
-  '重置订阅信息': '구독 재설정',
+  '该订阅长期有效': '구독은 무제한으로 유효합니다',
+  '已过期': '만료',
+  '已用 {used} / 总计 {total}': '{date}에 만료됩니다, 만료 {day}이 전, {reset_day}후 데이터 전송 재설정',
+  '重置订阅信息': '구독 재설정하기',
   '没有可用节点,如果您未订阅或已过期请': '사용 가능한 액세스 포인트가 없습니다. 구독을 신청하지 않았거나 구독이 만료된 경우',
   '订阅': '구독',
-  '确定要重置当月流量?': '이번 달의 이체 데이터를 재설정하시겠습니까?',
+  '确定重置当前已用流量?': '确定重置当前已用流量?',
   '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '확인"을 클릭하면 결제 페이지로 이동됩니다. 주문이 완료되면 시스템에서 해당 월의 사용 데이터를 삭제합니다.',
   '确定': '확인',
   '确定要重置订阅信息?': '구독을 재설정하시겠습니까?',
-  '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': '계정 정보 또는 구독 정보가 누출된 경우 이 옵션을 사용하여 UUID를 재설정하며 재설정 후 구독이 변경되므로 다시 구독해야 합니다.',
+  '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': '계정 정보나 구독이 누출된 경우 이 옵션은 UUID를 재설정하는 데 사용되며 재설정 후에 구독이 변경되므로 다시 구독해야 합니다.',
   '重置成功': '재설정 성공',
   '低': '낮음',
   '中': '중간',
@@ -179,7 +179,7 @@ window.settings.i18n['ko-KR'] = {
   '实际下行': '실제 다운로드',
   '合计': '전체',
   '公式:(实际上行 + 实际下行) x 扣费倍率 = 扣除流量': '공식: (실제 업로드 + 실제 다운로드) x 공제율 = 전송 데이터 공제',
-  '复制成功': '성공적으로 복사',
+  '复制成功': '성공적으로 복사',
   '复制订阅地址': '구독 URL 복사',
   '导入到': '내보내기',
   '一键订阅': '빠른 구독',
@@ -199,20 +199,18 @@ window.settings.i18n['ko-KR'] = {
   '提现账号': '인출 계좌',
   '请输入提现账号': '인출 계좌를 입력해주세요',
   '我知道了': '알겠습니다.',
-  '绑定Telegram': '텔레그램 열기 및 탐색',
+  '绑定Telegram': '탤레그램으로 연결',
   '第一步': '첫번째 단계',
   '第二步': '두번째 단계',
   '打开Telegram搜索': '텔레그램 열기 및 탐색',
   '向机器人发送你的': '봇에 다음 명령을 보냅니다',
   '使用文档': '사용 설명서',
   '最后更新: {date}': '마지막 업데이트{date}',
-  '复制成功': '복사 성공',
-  '我的订阅': '나의 구독',
+  '复制成功': '성공적으로 복사',
   '还有没支付的订单': '미결제 주문이 있습니다',
   '立即支付': '즉시 지불',
   '条工单正在处理中': '티켓이 처리 중입니다',
   '立即查看': '제목을 입력하세요',
-  '购买订阅': '구독 구매 내역',
   '使用文档': '사용 설명서',
   '我的订单': '나의 주문',
   '流量明细': '데이터 세부 정보 전송',
@@ -233,7 +231,11 @@ window.settings.i18n['ko-KR'] = {
   '余额支付': '잔액 지불',
   '我的工单': '나의 티켓',
   '工单历史': '티켓 기록',
-  '{reset_day} 日后重置流量': '{reset_day} 일 후 플로우 재설정',
+  '已用流量将在 {reset_day} 日后重置': '已用流量将在 {reset_day} 日后重置',
+  '已用流量已在今日重置': '已用流量已在今日重置',
+  '重置已用流量': '重置已用流量',
+  '查看节点状态': '查看节点状态',
+  '当前已使用流量达{rate}%': '当前已使用流量达{rate}%',
   '节点名称': '환불 금액',
   '于 {date} 到期,距离到期还有 {day} 天。': '{day}까지, 만료 {day}일 전.',
   'Telegram 讨论组': '텔레그램으로 문의하세요',
@@ -263,7 +265,13 @@ window.settings.i18n['ko-KR'] = {
   '选择最适合你的计划': '选择最适合你的计划',
   '全部': '全部',
   '按周期': '按周期',
-  '一次性': '一次性',
   '遇到问题': '遇到问题',
-  '遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通'
+  '遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通',
+  '按流量': '按流量',
+  '搜索文档': '搜索文档',
+  '技术支持': '技术支持',
+  '当前剩余佣金': '当前剩余佣金',
+  '三级分销比例': '三级分销比例',
+  '累计获得佣金': '累计获得佣金',
+  '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。'
 };

+ 46 - 38
public/theme/v2board/assets/i18n/vi-VN.js

@@ -9,7 +9,7 @@ window.settings.i18n['vi-VN'] = {
   '一次性': 'Dài Hạn',
   '重置流量包': 'Cập Nhật Dung Lượng',
   '待支付': 'Đợi Thanh Toán',
-  '开通中': 'Khai Mạc',
+  '开通中': 'Đang xử lý',
   '已取消': 'Đã Hủy',
   '已完成': 'Thực Hiện',
   '已折抵': 'Quy Đổi',
@@ -80,11 +80,11 @@ window.settings.i18n['vi-VN'] = {
   '总计': 'Tổng',
   '结账': 'Kết toán',
   '等待支付中': 'Đang chờ thanh toán',
-  '开通中': 'Đang mở',
+  '开通中': 'Đang xử lý',
   '订单系统正在进行处理,请稍等1-3分钟。': 'Hệ thống đang xử lý đơn hàng, vui lòng đợi 1-3p.',
-  '已取消': 'Đã hủy',
+  '已取消': 'Đã Hủy',
   '订单由于超时支付已被取消。': 'Do quá giờ nên đã hủy đơn hàng.',
-  '已完成': 'Đã hoàn thành',
+  '已完成': 'Thực Hiện',
   '订单已支付并开通。': 'Đơn hàng đã thanh toán và mở.',
   '选择订阅': 'Chọn gói',
   '立即订阅': 'Mua gói ngay',
@@ -128,7 +128,7 @@ window.settings.i18n['vi-VN'] = {
   '发送': 'Gửi',
   '邀请码': 'Mã mời',
   '邀请码(选填)': 'Mã mời(Điền)',
-  '注册': 'Đăng Ký',
+  '注册': 'Đăng ký',
   '返回登入': 'Về đăng nhập',
   '我已阅读并同意 <a target="_blank" href="{url}">服务条款</a>': 'Tôi đã đọc và đồng ý <a target="_blank" href="{url}">điều khoản dịch vụ</a>',
   '请同意服务条款': 'Hãy đồng ý điều khoản dịch vụ',
@@ -142,16 +142,16 @@ window.settings.i18n['vi-VN'] = {
   '复制成功': 'Sao chép thành công',
   '复制链接': 'Sao chép đường dẫn',
   '该订阅长期有效': 'Gói này có thời hạn dài',
-  '已过期': 'Đã hết hạn',
-  '已用 {used} / 总计 {total}': 'Đã dùng {used} / Tổng {total}',
+  '已过期': 'Tài khoản hết hạn',
+  '已用 {used} / 总计 {total}': 'Đã sử dụng {used} / Tổng dung lượng {total}',
   '重置订阅信息': 'Reset thông tin gói',
   '没有可用节点,如果您未订阅或已过期请': 'Chưa có node khả dụng, nếu bạn chưa mua gói hoặc đã hết hạn hãy',
-  '订阅': 'Mua Gói',
-  '确定要重置当月流量?': 'Xác nhận muốn reset dung lượng tháng này?',
+  '订阅': 'Gói Dịch Vụ',
+  '确定重置当前已用流量?': '确定重置当前已用流量?',
   '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': 'Ấn 「OK」 sẽ chuyển đến trang thanh toán, sau khi thanh toán đơn hàng hệ thống sẽ xóa dung lượng đã dùng tháng này của bạn.',
   '确定': 'OK',
-  '确定要重置订阅信息?': 'Xác nhận reset thông tin gói dịch vụ?',
-  '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': 'Nếu địa chỉ hoặc thông tin gói dịch vụ của bạn bị tiết lộ có thể tiến hành thao tác này. Sau khi reset Uuid sẽ thay đổi.',
+  '确定要重置订阅信息?': 'Xác nhận reset thông tin gói?',
+  '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': 'Nếu địa chỉ hoặc thông tin gói dịch vụ của bạn bị tiết lộ có thể tiến hành thao tác này. Sau khi reset UUID sẽ thay đổi.',
   '重置成功': 'Reset thành công',
   '低': 'Thấp',
   '中': 'Vừa',
@@ -204,15 +204,13 @@ window.settings.i18n['vi-VN'] = {
   '第二步': 'Bước 2',
   '打开Telegram搜索': 'Mở Telegram tìm kiếm',
   '向机器人发送你的': 'Gửi cho bot',
-  '使用文档': 'Tài liệu sử dụng',
+  '使用文档': 'Kiến Thức Cơ Bản',
   '最后更新: {date}': 'Cập nhật gần đây: {date}',
   '复制成功': 'Sao chép thành công',
-  '我的订阅': 'Gói Dịch Vụ Của Tôi',
   '还有没支付的订单': 'Có đơn hàng chưa thanh toán',
   '立即支付': 'Thanh toán ngay',
   '条工单正在处理中': ' công việc đang xử lý',
   '立即查看': 'Xem Ngay',
-  '购买订阅': 'Mua Gói Dịch Vụ',
   '使用文档': 'Tài liệu sử dụng',
   '我的订单': 'Đơn Hàng Của Tôi',
   '流量明细': 'Chi Tiết Dung Lượng',
@@ -233,7 +231,11 @@ window.settings.i18n['vi-VN'] = {
   '余额支付': 'Thanh toán số dư',
   '我的工单': 'Liên Hệ Với Chúng Tôi',
   '工单历史': 'Lịch sử đơn hàng',
-  '{reset_day} 日后重置流量': '{reset_day} ngày sau reset dung lượng',
+  '已用流量将在 {reset_day} 日后重置': '已用流量将在 {reset_day} 日后重置',
+  '已用流量已在今日重置': '已用流量已在今日重置',
+  '重置已用流量': '重置已用流量',
+  '查看节点状态': '查看节点状态',
+  '当前已使用流量达{rate}%': '当前已使用流量达{rate}%',
   '节点名称': 'Tên node',
   '于 {date} 到期,距离到期还有 {day} 天。': 'Hết hạn vào {date}, còn {day} ngày.',
   'Telegram 讨论组': 'Nhóm Telegram',
@@ -243,27 +245,33 @@ window.settings.i18n['vi-VN'] = {
   '流量明细仅保留近月数据以供查询。': 'Chi tiết dung lượng chỉ lưu dữ liệu của những tháng gần đây để truy vấn.',
   '扣费倍率': 'Tỷ lệ khấu trừ',
   '支付手续费': 'Phí thủ tục',
-  '续费订阅': '续费订阅',
-  '学习如何使用': '学习如何使用',
-  '快速将节点导入对应客户端进行使用': '快速将节点导入对应客户端进行使用',
-  '对您当前的订阅进行续费': '对您当前的订阅进行续费',
-  '对您当前的订阅进行购买': '对您当前的订阅进行购买',
-  '捷径': '捷径',
-  '不会使用,查看使用教程': '不会使用,查看使用教程',
-  '使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅',
-  '扫描二维码订阅': '扫描二维码订阅',
-  '续费': '续费',
-  '购买': '购买',
-  '查看教程': '查看教程',
-  '注意': '注意',
-  '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?',
-  '确定取消': '确定取消',
-  '返回我的订单': '返回我的订单',
-  '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?',
-  '选择最适合你的计划': '选择最适合你的计划',
-  '全部': '全部',
-  '按周期': '按周期',
-  '一次性': '一次性',
-  '遇到问题': '遇到问题',
-  '遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通'
+  '续费订阅': 'Gia hạn đăng ký',
+  '学习如何使用': 'Hướng dẫn sử dụng',
+  '快速将节点导入对应客户端进行使用': 'Bạn cần phải mua gói này',
+  '对您当前的订阅进行续费': 'Gia hạn gói hiện tại',
+  '对您当前的订阅进行购买': 'Mua gói bạn đã chọn',
+  '捷径': 'Phím tắt',
+  '不会使用,查看使用教程': 'Mua gói này nếu bạn đăng ký',
+  '使用支持扫码的客户端进行订阅': 'Sử dụng ứng dụng quét mã để đăng ký',
+  '扫描二维码订阅': 'Quét mã QR để đăng ký',
+  '续费': 'Gia hạn',
+  '购买': 'Mua',
+  '查看教程': 'Xem hướng dẫn',
+  '注意': 'Chú Ý',
+  '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': 'Bạn vẫn còn đơn đặt hàng chưa hoàn thành. Bạn cần hủy trước khi mua. Bạn có chắc chắn muốn hủy đơn đặt hàng trước đó không ?',
+  '确定取消': 'Đúng/không',
+  '返回我的订单': 'Quay lại đơn đặt hàng của tôi',
+  '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': 'Nếu bạn đã thanh toán, việc hủy đơn hàng có thể khiến việc thanh toán không thành công. Bạn có chắc chắn muốn hủy đơn hàng không ?',
+  '选择最适合你的计划': 'Chọn kế hoạch phù hợp với bạn nhất',
+  '全部': 'Tất cả',
+  '按周期': 'Chu kỳ',
+  '遇到问题': 'Chúng tôi có một vấn đề',
+  '遇到问题可以通过工单与我们沟通': 'Nếu bạn gặp sự cố, bạn có thể liên lạc với chúng tôi thông qua ',
+  '按流量': '按流量',
+  '搜索文档': '搜索文档',
+  '技术支持': '技术支持',
+  '当前剩余佣金': '当前剩余佣金',
+  '三级分销比例': '三级分销比例',
+  '累计获得佣金': '累计获得佣金',
+  '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。'
 };

+ 14 - 6
public/theme/v2board/assets/i18n/zh-CN.js

@@ -147,7 +147,7 @@ window.settings.i18n['zh-CN'] = {
   '重置订阅信息': '重置订阅信息',
   '没有可用节点,如果您未订阅或已过期请': '没有可用节点,如果您未订阅或已过期请',
   '订阅': '订阅',
-  '确定要重置当月流量?': '确定要重置当月流量?',
+  '确定重置当前已用流量?': '确定重置当前已用流量?',
   '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。',
   '确定': '确定',
   '确定要重置订阅信息?': '确定要重置订阅信息?',
@@ -207,12 +207,10 @@ window.settings.i18n['zh-CN'] = {
   '使用文档': '使用文档',
   '最后更新: {date}': '最后更新: {date}',
   '复制成功': '复制成功',
-  '我的订阅': '我的订阅',
   '还有没支付的订单': '还有没支付的订单',
   '立即支付': '立即支付',
   '条工单正在处理中': '条工单正在处理中',
   '立即查看': '立即查看',
-  '购买订阅': '购买订阅',
   '使用文档': '使用文档',
   '我的订单': '我的订单',
   '流量明细': '流量明细',
@@ -233,7 +231,11 @@ window.settings.i18n['zh-CN'] = {
   '余额支付': '余额支付',
   '我的工单': '我的工单',
   '工单历史': '工单历史',
-  '{reset_day} 日后重置流量': '{reset_day} 日后重置流量',
+  '已用流量将在 {reset_day} 日后重置': '已用流量将在 {reset_day} 日后重置',
+  '已用流量已在今日重置': '已用流量已在今日重置',
+  '重置已用流量': '重置已用流量',
+  '查看节点状态': '查看节点状态',
+  '当前已使用流量达{rate}%': '当前已使用流量达 {rate}%',
   '节点名称': '节点名称',
   '于 {date} 到期,距离到期还有 {day} 天。': '于 {date} 到期,距离到期还有 {day} 天。',
   'Telegram 讨论组': 'Telegram 讨论组',
@@ -263,7 +265,13 @@ window.settings.i18n['zh-CN'] = {
   '选择最适合你的计划': '选择最适合您的计划',
   '全部': '全部',
   '按周期': '按周期',
-  '一次性': '一次性',
   '遇到问题': '遇到问题',
-  '遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通'
+  '遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通',
+  '按流量': '按流量',
+  '搜索文档': '搜索文档',
+  '技术支持': '技术支持',
+  '当前剩余佣金': '当前剩余佣金',
+  '三级分销比例': '三级分销比例',
+  '累计获得佣金': '累计获得佣金',
+  '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。'
 };

+ 15 - 7
public/theme/v2board/assets/i18n/zh-TW.js

@@ -147,7 +147,7 @@ window.settings.i18n['zh-TW'] = {
   '重置订阅信息': '重置訂閲資訊',
   '没有可用节点,如果您未订阅或已过期请': '沒有可用節點,如果您未訂閱或已過期請',
   '订阅': '訂閱',
-  '确定要重置当月流量?': '確定要重置當月流量?',
+  '确定重置当前已用流量?': '確定重置當前已用流量?',
   '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '點擊「確定」將會跳轉到收銀台,支付訂單後系統將會清空您當月已使用流量。',
   '确定': '確定',
   '确定要重置订阅信息?': '確定要重置訂閱資訊?',
@@ -204,15 +204,13 @@ window.settings.i18n['zh-TW'] = {
   '第二步': '步驟二',
   '打开Telegram搜索': '打開 Telegram 並搜索',
   '向机器人发送你的': '向機器人發送您的',
-  '使用文档': '使用檔案',
+  '使用文档': '說明文件',
   '最后更新: {date}': '最後更新: {date}',
   '复制成功': '複製成功',
-  '我的订阅': '我的訂閱',
   '还有没支付的订单': '還有未支付的訂單',
   '立即支付': '立即支付',
   '条工单正在处理中': '條工單正在處理中',
   '立即查看': '立即檢視',
-  '购买订阅': '購買訂閲',
   '使用文档': '使用檔案',
   '我的订单': '我的訂單',
   '流量明细': '流量明細',
@@ -233,7 +231,11 @@ window.settings.i18n['zh-TW'] = {
   '余额支付': '餘額支付',
   '我的工单': '我的工單',
   '工单历史': '工單歷史',
-  '{reset_day} 日后重置流量': '{reset_day} 日後重置流量',
+  '已用流量将在 {reset_day} 日后重置': '已用流量將在 {reset_day} 日后重置',
+  '已用流量已在今日重置': '已用流量已在今日重置',
+  '重置已用流量': '重置已用流量',
+  '查看节点状态': '查看節點狀態',
+  '当前已使用流量达{rate}%': '當前已用流量達 {rate}%',
   '节点名称': '節點名稱',
   '于 {date} 到期,距离到期还有 {day} 天。': '於 {date} 到期,距離到期還有 {day} 天。',
   'Telegram 讨论组': 'Telegram 討論組',
@@ -263,7 +265,13 @@ window.settings.i18n['zh-TW'] = {
   '选择最适合你的计划': '選擇最適合您的計劃',
   '全部': '全部',
   '按周期': '按週期',
-  '一次性': '一次性',
   '遇到问题': '遇到問題',
-  '遇到问题可以通过工单与我们沟通': '遇到問題您可以通過工單與我們溝通'
+  '遇到问题可以通过工单与我们沟通': '遇到問題您可以通過工單與我們溝通',
+  '按流量': '按流量',
+  '搜索文档': '搜尋文檔',
+  '技术支持': '技術支援',
+  '当前剩余佣金': '当前剩余佣金',
+  '三级分销比例': '三级分销比例',
+  '累计获得佣金': '累计获得佣金',
+  '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。'
 };

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/theme/v2board/assets/umi.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
public/theme/v2board/assets/umi.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 7
public/vendor/horizon/app-dark.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 7
public/vendor/horizon/app.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 1
public/vendor/horizon/app.js


BIN
public/vendor/horizon/img/favicon.png


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 1
public/vendor/horizon/img/horizon.svg


+ 0 - 806
public/vendor/horizon/img/sprite.svg

@@ -1,806 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <symbol viewBox="0 0 20 20" id="zondicon-add-outline">
-        <path d="M11 9h4v2h-4v4H9v-4H5V9h4V5h2v4zm-1 11a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-add-solid">
-        <path d="M11 9V5H9v4H5v2h4v4h2v-4h4V9h-4zm-1 11a10 10 0 1 1 0-20 10 10 0 0 1 0 20z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-adjust">
-        <path d="M10 2v16a8 8 0 1 0 0-16zm0 18a10 10 0 1 1 0-20 10 10 0 0 1 0 20z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-airplane">
-        <path d="M8.4 12H2.8L1 15H0V5h1l1.8 3h5.6L6 0h2l4.8 8H18a2 2 0 1 1 0 4h-5.2L8 20H6l2.4-8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-align-center">
-        <path d="M1 1h18v2H1V1zm0 8h18v2H1V9zm0 8h18v2H1v-2zM4 5h12v2H4V5zm0 8h12v2H4v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-align-justified">
-        <path d="M1 1h18v2H1V1zm0 8h18v2H1V9zm0 8h18v2H1v-2zM1 5h18v2H1V5zm0 8h18v2H1v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-align-left">
-        <path d="M1 1h18v2H1V1zm0 8h18v2H1V9zm0 8h18v2H1v-2zM1 5h12v2H1V5zm0 8h12v2H1v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-align-right">
-        <path d="M1 1h18v2H1V1zm0 8h18v2H1V9zm0 8h18v2H1v-2zM7 5h12v2H7V5zm0 8h12v2H7v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-anchor">
-        <path d="M4.34 15.66A7.97 7.97 0 0 0 9 17.94V10H5V8h4V5.83a3 3 0 1 1 2 0V8h4v2h-4v7.94a7.97 7.97 0 0 0 4.66-2.28l-1.42-1.42h5.66l-2.83 2.83a10 10 0 0 1-14.14 0L.1 14.24h5.66l-1.42 1.42zM10 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-announcement">
-        <path d="M3 6c0-1.1.9-2 2-2h8l4-4h2v16h-2l-4-4H5a2 2 0 0 1-2-2H1V6h2zm8 9v5H8l-1.67-5H5v-2h8v2h-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-apparel">
-        <path d="M7 0H6L0 3v6l4-1v12h12V8l4 1V3l-6-3h-1a3 3 0 0 1-6 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-outline-down">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-2-8V5h4v5h3l-5 5-5-5h3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-outline-left">
-        <path d="M0 10a10 10 0 1 1 20 0 10 10 0 0 1-20 0zm2 0a8 8 0 1 0 16 0 8 8 0 0 0-16 0zm8-2h5v4h-5v3l-5-5 5-5v3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-outline-right">
-        <path d="M20 10a10 10 0 1 1-20 0 10 10 0 0 1 20 0zm-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0zm-8 2H5V8h5V5l5 5-5 5v-3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-outline-up">
-        <path d="M10 0a10 10 0 1 1 0 20 10 10 0 0 1 0-20zm0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16zm2 8v5H8v-5H5l5-5 5 5h-3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-thick-down">
-        <path d="M7 10V2h6v8h5l-8 8-8-8h5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-thick-left">
-        <path d="M10 13h8V7h-8V2l-8 8 8 8v-5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-thick-right">
-        <path d="M10 7H2v6h8v5l8-8-8-8v5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-thick-up">
-        <path d="M7 10v8h6v-8h5l-8-8-8 8h5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-thin-down">
-        <path d="M9 16.172l-6.071-6.071-1.414 1.414L10 20l.707-.707 7.778-7.778-1.414-1.414L11 16.172V0H9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-thin-left">
-        <path d="M3.828 9l6.071-6.071-1.414-1.414L0 10l.707.707 7.778 7.778 1.414-1.414L3.828 11H20V9H3.828z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-thin-right">
-        <path d="M16.172 9l-6.071-6.071 1.414-1.414L20 10l-.707.707-7.778 7.778-1.414-1.414L16.172 11H0V9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-arrow-thin-up">
-        <path d="M9 3.828L2.929 9.899 1.515 8.485 10 0l.707.707 7.778 7.778-1.414 1.414L11 3.828V20H9V3.828z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-at-symbol">
-        <path d="M13.6 13.47A4.99 4.99 0 0 1 5 10a5 5 0 0 1 8-4V5h2v6.5a1.5 1.5 0 0 0 3 0V10a8 8 0 1 0-4.42 7.16l.9 1.79A10 10 0 1 1 20 10h-.18.17v1.5a3.5 3.5 0 0 1-6.4 1.97zM10 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-attachment">
-        <path d="M15 3H7a7 7 0 1 0 0 14h8v-2H7A5 5 0 0 1 7 5h8a3 3 0 0 1 0 6H7a1 1 0 0 1 0-2h8V7H7a3 3 0 1 0 0 6h8a5 5 0 0 0 0-10z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-backspace">
-        <path d="M0 10l7-7h13v14H7l-7-7zm14.41 0l2.13-2.12-1.42-1.42L13 8.6l-2.12-2.13-1.42 1.42L11.6 10l-2.13 2.12 1.42 1.42L13 11.4l2.12 2.13 1.42-1.42L14.4 10z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-badge">
-        <path d="M10 12a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-3a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm4 2.75V20l-4-4-4 4v-8.25a6.97 6.97 0 0 0 8 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-battery-full">
-        <path d="M0 6c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm2 0v8h16V6H2zm1 1h4v6H3V7zm5 0h4v6H8V7zm5 0h4v6h-4V7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-battery-half">
-        <path d="M0 6c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm2 0v8h16V6H2zm1 1h4v6H3V7zm5 0h4v6H8V7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-battery-low">
-        <path d="M0 6c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm2 0v8h16V6H2zm1 1h4v6H3V7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-beverage">
-        <path d="M9 18v-7L0 2V0h20v2l-9 9v7l5 1v1H4v-1l5-1zm2-10a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-block">
-        <path d="M0 10a10 10 0 1 1 20 0 10 10 0 0 1-20 0zm16.32-4.9L5.09 16.31A8 8 0 0 0 16.32 5.09zm-1.41-1.42A8 8 0 0 0 3.68 14.91L14.91 3.68z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-bluetooth">
-        <path d="M9.41 0l6 6-4 4 4 4-6 6H9v-7.59l-3.3 3.3-1.4-1.42L8.58 10l-4.3-4.3L5.7 4.3 9 7.58V0h.41zM11 4.41V7.6L12.59 6 11 4.41zM12.59 14L11 12.41v3.18L12.59 14z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-bolt">
-        <path d="M13 8V0L8.11 5.87 3 12h4v8L17 8h-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-book-reference">
-        <path d="M6 4H5a1 1 0 1 1 0-2h11V1a1 1 0 0 0-1-1H4a2 2 0 0 0-2 2v16c0 1.1.9 2 2 2h12a2 2 0 0 0 2-2V5a1 1 0 0 0-1-1h-7v8l-2-2-2 2V4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-bookmark">
-        <path d="M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4V2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-bookmark-outline">
-        <path d="M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4V2zm2 0v15l6-3 6 3V2H4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-bookmark-outline-add">
-        <path d="M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4V2zm2 0v15l6-3 6 3V2H4zm5 5V5h2v2h2v2h-2v2H9V9H7V7h2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-all">
-        <path d="M11 11v6h6v-6h-6zm0-2h6V3h-6v6zm-2 2H3v6h6v-6zm0-2V3H3v6h6zm-8 9V1h18v18H1v-1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-bottom">
-        <path d="M1 1h2v2H1V1zm0 4h2v2H1V5zm0 4h2v2H1V9zm0 4h2v2H1v-2zm0 4h18v2H1v-2zM5 1h2v2H5V1zm0 8h2v2H5V9zm4-8h2v2H9V1zm0 4h2v2H9V5zm0 4h2v2H9V9zm0 4h2v2H9v-2zm4-12h2v2h-2V1zm0 8h2v2h-2V9zm4-8h2v2h-2V1zm0 4h2v2h-2V5zm0 4h2v2h-2V9zm0 4h2v2h-2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-horizontal">
-        <path d="M1 1h2v2H1V1zm0 4h2v2H1V5zm0 4h18v2H1V9zm0 4h2v2H1v-2zm0 4h2v2H1v-2zM5 1h2v2H5V1zm0 16h2v2H5v-2zM9 1h2v2H9V1zm0 4h2v2H9V5zm0 8h2v2H9v-2zm0 4h2v2H9v-2zm4-16h2v2h-2V1zm0 16h2v2h-2v-2zm4-16h2v2h-2V1zm0 4h2v2h-2V5zm0 8h2v2h-2v-2zm0 4h2v2h-2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-inner">
-        <path d="M9 9V1h2v8h8v2h-8v8H9v-8H1V9h8zM1 1h2v2H1V1zm0 4h2v2H1V5zm0 8h2v2H1v-2zm0 4h2v2H1v-2zM5 1h2v2H5V1zm0 16h2v2H5v-2zm8-16h2v2h-2V1zm0 16h2v2h-2v-2zm4-16h2v2h-2V1zm0 4h2v2h-2V5zm0 8h2v2h-2v-2zm0 4h2v2h-2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-left">
-        <path d="M1 1h2v18H1V1zm4 0h2v2H5V1zm0 8h2v2H5V9zm0 8h2v2H5v-2zM9 1h2v2H9V1zm0 4h2v2H9V5zm0 4h2v2H9V9zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm4-16h2v2h-2V1zm0 8h2v2h-2V9zm0 8h2v2h-2v-2zm4-16h2v2h-2V1zm0 4h2v2h-2V5zm0 4h2v2h-2V9zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-none">
-        <path d="M1 1h2v2H1V1zm0 4h2v2H1V5zm0 4h2v2H1V9zm0 4h2v2H1v-2zm0 4h2v2H1v-2zM5 1h2v2H5V1zm0 8h2v2H5V9zm0 8h2v2H5v-2zM9 1h2v2H9V1zm0 4h2v2H9V5zm0 4h2v2H9V9zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm4-16h2v2h-2V1zm0 8h2v2h-2V9zm0 8h2v2h-2v-2zm4-16h2v2h-2V1zm0 4h2v2h-2V5zm0 4h2v2h-2V9zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-outer">
-        <path d="M2 19H1V1h18v18H2zm1-2h14V3H3v14zm10-8h2v2h-2V9zM9 9h2v2H9V9zM5 9h2v2H5V9zm4-4h2v2H9V5zm0 8h2v2H9v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-right">
-        <path d="M5 1h2v2H5V1zm0 8h2v2H5V9zm0 8h2v2H5v-2zM9 1h2v2H9V1zm0 4h2v2H9V5zm0 4h2v2H9V9zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm4-16h2v2h-2V1zm0 8h2v2h-2V9zm0 8h2v2h-2v-2zM1 1h2v2H1V1zm0 4h2v2H1V5zm0 4h2v2H1V9zm0 4h2v2H1v-2zm0 4h2v2H1v-2zM17 1h2v18h-2V1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-top">
-        <path d="M1 1h18v2H1V1zm0 4h2v2H1V5zm0 4h2v2H1V9zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm4-8h2v2H5V9zm0 8h2v2H5v-2zM9 5h2v2H9V5zm0 4h2v2H9V9zm0 4h2v2H9v-2zm0 4h2v2H9v-2zm4-8h2v2h-2V9zm0 8h2v2h-2v-2zm4-12h2v2h-2V5zm0 4h2v2h-2V9zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-border-vertical">
-        <path d="M1 1h2v2H1V1zm0 4h2v2H1V5zm0 4h2v2H1V9zm0 4h2v2H1v-2zm0 4h2v2H1v-2zM5 1h2v2H5V1zm0 8h2v2H5V9zm0 8h2v2H5v-2zM9 1h2v18H9V1zm4 0h2v2h-2V1zm0 8h2v2h-2V9zm0 8h2v2h-2v-2zm4-16h2v2h-2V1zm0 4h2v2h-2V5zm0 4h2v2h-2V9zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-box">
-        <path d="M0 2C0 .9.9 0 2 0h16a2 2 0 0 1 2 2v2H0V2zm1 3h18v13a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V5zm6 2v2h6V7H7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-brightness-down">
-        <path d="M10 13a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM9 4a1 1 0 1 1 2 0 1 1 0 1 1-2 0zm4.54 1.05a1 1 0 1 1 1.41 1.41 1 1 0 1 1-1.41-1.41zM16 9a1 1 0 1 1 0 2 1 1 0 1 1 0-2zm-1.05 4.54a1 1 0 1 1-1.41 1.41 1 1 0 1 1 1.41-1.41zM11 16a1 1 0 1 1-2 0 1 1 0 1 1 2 0zm-4.54-1.05a1 1 0 1 1-1.41-1.41 1 1 0 1 1 1.41 1.41zM4 11a1 1 0 1 1 0-2 1 1 0 1 1 0 2zm1.05-4.54a1 1 0 1 1 1.41-1.41 1 1 0 1 1-1.41 1.41z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-brightness-up">
-        <path d="M10 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM9 1a1 1 0 1 1 2 0v2a1 1 0 1 1-2 0V1zm6.65 1.94a1 1 0 1 1 1.41 1.41l-1.4 1.4a1 1 0 1 1-1.41-1.41l1.4-1.4zM18.99 9a1 1 0 1 1 0 2h-1.98a1 1 0 1 1 0-2h1.98zm-1.93 6.65a1 1 0 1 1-1.41 1.41l-1.4-1.4a1 1 0 1 1 1.41-1.41l1.4 1.4zM11 18.99a1 1 0 1 1-2 0v-1.98a1 1 0 1 1 2 0v1.98zm-6.65-1.93a1 1 0 1 1-1.41-1.41l1.4-1.4a1 1 0 1 1 1.41 1.41l-1.4 1.4zM1.01 11a1 1 0 1 1 0-2h1.98a1 1 0 1 1 0 2H1.01zm1.93-6.65a1 1 0 1 1 1.41-1.41l1.4 1.4a1 1 0 1 1-1.41 1.41l-1.4-1.4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-browser-window">
-        <path d="M0 3c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm2 2v12h16V5H2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-browser-window-new">
-        <path d="M9 10V8h2v2h2v2h-2v2H9v-2H7v-2h2zM0 3c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm2 2v12h16V5H2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-browser-window-open">
-        <path d="M0 3c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm2 2v12h16V5H2zm8 3l4 5H6l4-5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-bug">
-        <path d="M15.3 14.89l2.77 2.77a1 1 0 0 1 0 1.41 1 1 0 0 1-1.41 0l-2.59-2.58A5.99 5.99 0 0 1 11 18V9.04a1 1 0 0 0-2 0V18a5.98 5.98 0 0 1-3.07-1.51l-2.59 2.58a1 1 0 0 1-1.41 0 1 1 0 0 1 0-1.41l2.77-2.77A5.95 5.95 0 0 1 4.07 13H1a1 1 0 1 1 0-2h3V8.41L.93 5.34a1 1 0 0 1 0-1.41 1 1 0 0 1 1.41 0l2.1 2.1h11.12l2.1-2.1a1 1 0 0 1 1.41 0 1 1 0 0 1 0 1.41L16 8.41V11h3a1 1 0 1 1 0 2h-3.07c-.1.67-.32 1.31-.63 1.89zM15 5H5a5 5 0 1 1 10 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-buoy">
-        <path d="M17.16 6.42a8.03 8.03 0 0 0-3.58-3.58l-1.34 2.69a5.02 5.02 0 0 1 2.23 2.23l2.69-1.34zm0 7.16l-2.69-1.34a5.02 5.02 0 0 1-2.23 2.23l1.34 2.69a8.03 8.03 0 0 0 3.58-3.58zM6.42 2.84a8.03 8.03 0 0 0-3.58 3.58l2.69 1.34a5.02 5.02 0 0 1 2.23-2.23L6.42 2.84zM2.84 13.58a8.03 8.03 0 0 0 3.58 3.58l1.34-2.69a5.02 5.02 0 0 1-2.23-2.23l-2.69 1.34zM10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-7a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-calculator">
-        <path d="M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm3 1v2h10V3H5zm0 4v2h2V7H5zm4 0v2h2V7H9zm4 0v2h2V7h-2zm-8 4v2h2v-2H5zm4 0v2h2v-2H9zm4 0v6h2v-6h-2zm-8 4v2h2v-2H5zm4 0v2h2v-2H9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-calendar">
-        <path d="M1 4c0-1.1.9-2 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V4zm2 2v12h14V6H3zm2-6h2v2H5V0zm8 0h2v2h-2V0zM5 9h2v2H5V9zm0 4h2v2H5v-2zm4-4h2v2H9V9zm0 4h2v2H9v-2zm4-4h2v2h-2V9zm0 4h2v2h-2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-camera">
-        <path d="M0 6c0-1.1.9-2 2-2h3l2-2h6l2 2h3a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm10 10a5 5 0 1 0 0-10 5 5 0 0 0 0 10zm0-2a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-chart">
-        <path d="M4.13 12H4a2 2 0 1 0 1.8 1.11L7.86 10a2.03 2.03 0 0 0 .65-.07l1.55 1.55a2 2 0 1 0 3.72-.37L15.87 8H16a2 2 0 1 0-1.8-1.11L12.14 10a2.03 2.03 0 0 0-.65.07L9.93 8.52a2 2 0 1 0-3.72.37L4.13 12zM0 4c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-chart-bar">
-        <path d="M1 10h3v10H1V10zM6 0h3v20H6V0zm5 8h3v12h-3V8zm5-4h3v16h-3V4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-chart-pie">
-        <path d="M19.95 11A10 10 0 1 1 9 .05V11h10.95zm-.08-2.6H11.6V.13a10 10 0 0 1 8.27 8.27z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-chat-bubble-dots">
-        <path d="M10 15l-4 4v-4H2a2 2 0 0 1-2-2V3c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-8zM5 7v2h2V7H5zm4 0v2h2V7H9zm4 0v2h2V7h-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-checkmark">
-        <path d="M0 11l2-2 5 5L18 3l2 2L7 18z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-checkmark-outline">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM6.7 9.29L9 11.6l4.3-4.3 1.4 1.42L9 14.4l-3.7-3.7 1.4-1.42z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cheveron-down">
-        <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cheveron-left">
-        <path d="M7.05 9.293L6.343 10 12 15.657l1.414-1.414L9.172 10l4.242-4.243L12 4.343z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cheveron-outline-down">
-        <path d="M20 10a10 10 0 1 1-20 0 10 10 0 0 1 20 0zM10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16zm-.7 10.54L5.75 9l1.41-1.41L10 10.4l2.83-2.82L14.24 9 10 13.24l-.7-.7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cheveron-outline-left">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm8-10a8 8 0 1 0-16 0 8 8 0 0 0 16 0zM7.46 9.3L11 5.75l1.41 1.41L9.6 10l2.82 2.83L11 14.24 6.76 10l.7-.7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cheveron-outline-right">
-        <path d="M10 0a10 10 0 1 1 0 20 10 10 0 0 1 0-20zM2 10a8 8 0 1 0 16 0 8 8 0 0 0-16 0zm10.54.7L9 14.25l-1.41-1.41L10.4 10 7.6 7.17 9 5.76 13.24 10l-.7.7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cheveron-outline-up">
-        <path d="M0 10a10 10 0 1 1 20 0 10 10 0 0 1-20 0zm10 8a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm.7-10.54L14.25 11l-1.41 1.41L10 9.6l-2.83 2.8L5.76 11 10 6.76l.7.7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cheveron-right">
-        <path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cheveron-up">
-        <path d="M10.707 7.05L10 6.343 4.343 12l1.414 1.414L10 9.172l4.243 4.242L15.657 12z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-clipboard">
-        <path d="M7.03 2.6a3 3 0 0 1 5.94 0L15 3v1h1a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6c0-1.1.9-2 2-2h1V3l2.03-.4zM5 6H4v12h12V6h-1v1H5V6zm5-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-close">
-        <path d="M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-close-outline">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm1.41-1.41A8 8 0 1 0 15.66 4.34 8 8 0 0 0 4.34 15.66zm9.9-8.49L11.41 10l2.83 2.83-1.41 1.41L10 11.41l-2.83 2.83-1.41-1.41L8.59 10 5.76 7.17l1.41-1.41L10 8.59l2.83-2.83 1.41 1.41z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-close-solid">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM11.4 10l2.83-2.83-1.41-1.41L10 8.59 7.17 5.76 5.76 7.17 8.59 10l-2.83 2.83 1.41 1.41L10 11.41l2.83 2.83 1.41-1.41L11.41 10z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cloud">
-        <path d="M16.88 9.1A4 4 0 0 1 16 17H5a5 5 0 0 1-1-9.9V7a3 3 0 0 1 4.52-2.59A4.98 4.98 0 0 1 17 8c0 .38-.04.74-.12 1.1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cloud-upload">
-        <path d="M16.88 9.1A4 4 0 0 1 16 17H5a5 5 0 0 1-1-9.9V7a3 3 0 0 1 4.52-2.59A4.98 4.98 0 0 1 17 8c0 .38-.04.74-.12 1.1zM11 11h3l-4-4-4 4h3v3h2v-3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-code">
-        <path d="M.7 9.3l4.8-4.8 1.4 1.42L2.84 10l4.07 4.07-1.41 1.42L0 10l.7-.7zm18.6 1.4l.7-.7-5.49-5.49-1.4 1.42L17.16 10l-4.07 4.07 1.41 1.42 4.78-4.78z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-coffee">
-        <path d="M4 11H2a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h2V1h14v10a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4zm0-2V5H2v4h2zm-2 8v-1h18v1l-4 2H6l-4-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-cog">
-        <path d="M3.94 6.5L2.22 3.64l1.42-1.42L6.5 3.94c.52-.3 1.1-.54 1.7-.7L9 0h2l.8 3.24c.6.16 1.18.4 1.7.7l2.86-1.72 1.42 1.42-1.72 2.86c.3.52.54 1.1.7 1.7L20 9v2l-3.24.8c-.16.6-.4 1.18-.7 1.7l1.72 2.86-1.42 1.42-2.86-1.72c-.52.3-1.1.54-1.7.7L11 20H9l-.8-3.24c-.6-.16-1.18-.4-1.7-.7l-2.86 1.72-1.42-1.42 1.72-2.86c-.3-.52-.54-1.1-.7-1.7L0 11V9l3.24-.8c.16-.6.4-1.18.7-1.7zM10 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-color-palette">
-        <path d="M9 20v-1.7l.01-.24L15.07 12h2.94c1.1 0 1.99.89 1.99 2v4a2 2 0 0 1-2 2H9zm0-3.34V5.34l2.08-2.07a1.99 1.99 0 0 1 2.82 0l2.83 2.83a2 2 0 0 1 0 2.82L9 16.66zM0 1.99C0 .9.89 0 2 0h4a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zM4 17a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-compose">
-        <path d="M2 4v14h14v-6l2-2v10H0V2h10L8 4H2zm10.3-.3l4 4L8 16H4v-4l8.3-8.3zm1.4-1.4L16 0l4 4-2.3 2.3-4-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-computer-desktop">
-        <path d="M7 17H2a2 2 0 0 1-2-2V2C0 .9.9 0 2 0h16a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2h-5l4 2v1H3v-1l4-2zM2 2v11h16V2H2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-computer-laptop">
-        <path d="M18 16h2v1a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-1h2V4c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v12zM4 4v9h12V4H4zm4 11v1h4v-1H8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-conversation">
-        <path d="M17 11v3l-3-3H8a2 2 0 0 1-2-2V2c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-1zm-3 2v2a2 2 0 0 1-2 2H6l-3 3v-3H2a2 2 0 0 1-2-2V8c0-1.1.9-2 2-2h2v3a4 4 0 0 0 4 4h6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-credit-card">
-        <path d="M18 6V4H2v2h16zm0 4H2v6h16v-6zM0 4c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm4 8h4v2H4v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-currency-dollar">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm1-5h1a3 3 0 0 0 0-6H7.99a1 1 0 0 1 0-2H14V5h-3V3H9v2H8a3 3 0 1 0 0 6h4a1 1 0 1 1 0 2H6v2h3v2h2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-dashboard">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm-5.6-4.29a9.95 9.95 0 0 1 11.2 0 8 8 0 1 0-11.2 0zm6.12-7.64l3.02-3.02 1.41 1.41-3.02 3.02a2 2 0 1 1-1.41-1.41z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-date-add">
-        <path d="M15 2h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V4c0-1.1.9-2 2-2h2V0h2v2h6V0h2v2zM3 6v12h14V6H3zm6 5V9h2v2h2v2h-2v2H9v-2H7v-2h2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-dial-pad">
-        <path d="M5 4a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm5 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm5 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM5 9a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm5 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm5 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM5 14a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm5 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm5-6a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-directions">
-        <path d="M10 0l10 10-10 10L0 10 10 0zM6 10v3h2v-3h3v3l4-4-4-4v3H8a2 2 0 0 0-2 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-document">
-        <path d="M4 18h12V6h-4V2H4v16zm-2 1V0h12l4 4v16H2v-1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-document-add">
-        <path d="M9 10V8h2v2h2v2h-2v2H9v-2H7v-2h2zm-5 8h12V6h-4V2H4v16zm-2 1V0h12l4 4v16H2v-1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-dots-horizontal-double">
-        <path d="M10 9a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-dots-horizontal-triple">
-        <path d="M10 12a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0-6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 12a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-download">
-        <path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-duplicate">
-        <path d="M6 6V2c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-4v4a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8c0-1.1.9-2 2-2h4zm2 0h4a2 2 0 0 1 2 2v4h4V2H8v4zM2 8v10h10V8H2zm4 4v-2h2v2h2v2H8v2H6v-2H4v-2h2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-edit-copy">
-        <path d="M6 6V2c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-4v4a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8c0-1.1.9-2 2-2h4zm2 0h4a2 2 0 0 1 2 2v4h4V2H8v4zM2 8v10h10V8H2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-edit-crop">
-        <path d="M14 16H6a2 2 0 0 1-2-2V6H0V4h4V0h2v14h14v2h-4v4h-2v-4zm0-3V6H7V4h7a2 2 0 0 1 2 2v7h-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-edit-cut">
-        <path d="M9.77 11.5l5.34 3.91c.44.33 1.24.59 1.79.59H20L6.89 6.38A3.5 3.5 0 1 0 5.5 8.37L7.73 10 5.5 11.63a3.5 3.5 0 1 0 1.38 1.99l2.9-2.12zM3.5 7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm0 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM15.1 4.59A3.53 3.53 0 0 1 16.9 4H20l-7.5 5.5L10.45 8l4.65-3.41z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-edit-pencil">
-        <path d="M12.3 3.7l4 4L4 20H0v-4L12.3 3.7zm1.4-1.4L16 0l4 4-2.3 2.3-4-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-education">
-        <path d="M3.33 8L10 12l10-6-10-6L0 6h10v2H3.33zM0 8v8l2-2.22V9.2L0 8zm10 12l-5-3-2-1.2v-6l7 4.2 7-4.2v6L10 20z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-envelope">
-        <path d="M18 2a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4c0-1.1.9-2 2-2h16zm-4.37 9.1L20 16v-2l-5.12-3.9L20 6V4l-10 8L0 4v2l5.12 4.1L0 14v2l6.37-4.9L10 14l3.63-2.9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-exclamation-outline">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 5h2v6H9V5zm0 8h2v2H9v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-exclamation-solid">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM9 5v6h2V5H9zm0 8v2h2v-2H9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-explore">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zM7.88 7.88l-3.54 7.78 7.78-3.54 3.54-7.78-7.78 3.54zM10 11a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-factory">
-        <path d="M10.5 20H0V7l5 3.33V7l5 3.33V7l5 3.33V0h5v20h-9.5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-fast-forward">
-        <path d="M1 5l9 5-9 5V5zm9 0l9 5-9 5V5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-fast-rewind">
-        <path d="M19 5v10l-9-5 9-5zm-9 0v10l-9-5 9-5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-film">
-        <path d="M0 4c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm6 0v12h8V4H6zM2 5v2h2V5H2zm0 4v2h2V9H2zm0 4v2h2v-2H2zm14-8v2h2V5h-2zm0 4v2h2V9h-2zm0 4v2h2v-2h-2zM8 7l5 3-5 3V7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-filter">
-        <path d="M12 12l8-8V0H0v4l8 8v8l4-4v-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-flag">
-        <path d="M7.667 12H2v8H0V0h12l.333 2H20l-3 6 3 6H8l-.333-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-flashlight">
-        <path d="M13 7v11a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V7L5 5V3h10v2l-2 2zM9 8v1a1 1 0 1 0 2 0V8a1 1 0 0 0-2 0zM5 0h10v2H5V0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-folder">
-        <path d="M0 4c0-1.1.9-2 2-2h7l2 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-folder-outline">
-        <path d="M0 4c0-1.1.9-2 2-2h7l2 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm2 2v10h16V6H2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-folder-outline-add">
-        <path d="M0 4c0-1.1.9-2 2-2h7l2 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm2 2v10h16V6H2zm7 4V8h2v2h2v2h-2v2H9v-2H7v-2h2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-format-bold">
-        <path d="M3 19V1h8a5 5 0 0 1 3.88 8.16A5.5 5.5 0 0 1 11.5 19H3zm7.5-8H7v5h3.5a2.5 2.5 0 1 0 0-5zM7 4v4h3a2 2 0 1 0 0-4H7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-format-italic">
-        <path d="M8 1h9v2H8V1zm3 2h3L8 17H5l6-14zM2 17h9v2H2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-format-text-size">
-        <path d="M16 9v8h-2V9h-4V7h10v2h-4zM8 5v12H6V5H0V3h15v2H8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-format-underline">
-        <path d="M16 9A6 6 0 1 1 4 9V1h3v8a3 3 0 0 0 6 0V1h3v8zM2 17h16v2H2v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-gift">
-        <path d="M14.83 4H20v6h-1v10H1V10H0V4h5.17A3 3 0 0 1 10 .76 3 3 0 0 1 14.83 4zM8 10H3v8h5v-8zm4 0v8h5v-8h-5zM8 6H2v2h6V6zm4 0v2h6V6h-6zM8 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm4 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-globe">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm2-2.25a8 8 0 0 0 4-2.46V9a2 2 0 0 1-2-2V3.07a7.95 7.95 0 0 0-3-1V3a2 2 0 0 1-2 2v1a2 2 0 0 1-2 2v2h3a2 2 0 0 1 2 2v5.75zm-4 0V15a2 2 0 0 1-2-2v-1h-.5A1.5 1.5 0 0 1 4 10.5V8H2.25A8.01 8.01 0 0 0 8 17.75z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-hand-stop">
-        <path d="M17 16a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4.01V4a1 1 0 0 1 1-1 1 1 0 0 1 1 1v6h1V2a1 1 0 0 1 1-1 1 1 0 0 1 1 1v8h1V1a1 1 0 1 1 2 0v9h1V2a1 1 0 0 1 1-1 1 1 0 0 1 1 1v13h1V9a1 1 0 0 1 1-1h1v8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-hard-drive">
-        <path d="M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm10.4 5.6A5 5 0 1 0 15 12V5l-2.6 2.6zM10 14a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM6 3v2h4V3H6zM4 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 16a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm12 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0-16a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-headphones">
-        <path d="M16 8A6 6 0 1 0 4 8v11H2a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2V8a8 8 0 1 1 16 0v3a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2V8zm-4 2h3v10h-3V10zm-7 0h3v10H5V10z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-heart">
-        <path d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.78 7.77L10 18.78l8.39-8.4a5.5 5.5 0 0 0-7.78-7.77l-.61.61z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-home">
-        <path d="M8 20H3V10H0L10 0l10 10h-3v10h-5v-6H8v6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-hot">
-        <path d="M10 0s8 7.58 8 12a8 8 0 1 1-16 0c0-1.5.91-3.35 2.12-5.15A3 3 0 0 0 10 6V0zM8 0a3 3 0 1 0 0 6V0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-hour-glass">
-        <path d="M3 18a7 7 0 0 1 4-6.33V8.33A7 7 0 0 1 3 2H1V0h18v2h-2a7 7 0 0 1-4 6.33v3.34A7 7 0 0 1 17 18h2v2H1v-2h2zM5 2a5 5 0 0 0 4 4.9V10h2V6.9A5 5 0 0 0 15 2H5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-inbox">
-        <path d="M0 2C0 .9.9 0 2 0h16a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm14 12h4V2H2v12h4c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-inbox-check">
-        <path d="M0 2C0 .9.9 0 2 0h16a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm14 12h4V2H2v12h4c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2zM5 9l2-2 2 2 4-4 2 2-6 6-4-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-inbox-download">
-        <path d="M0 2C0 .9.9 0 2 0h16a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm14 12h4V2H2v12h4c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2zM9 8V5h2v3h3l-4 4-4-4h3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-inbox-full">
-        <path d="M14 14h4V2H2v12h4c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2zM0 2C0 .9.9 0 2 0h16a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm4 2h12v2H4V4zm0 3h12v2H4V7zm0 3h12v2H4v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-indent-decrease">
-        <path d="M1 1h18v2H1V1zm6 8h12v2H7V9zm-6 8h18v2H1v-2zM7 5h12v2H7V5zm0 8h12v2H7v-2zM5 6v8l-4-4 4-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-indent-increase">
-        <path d="M1 1h18v2H1V1zm6 8h12v2H7V9zm-6 8h18v2H1v-2zM7 5h12v2H7V5zm0 8h12v2H7v-2zM1 6l4 4-4 4V6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-information-outline">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 11V9h2v6H9v-4zm0-6h2v2H9V5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-information-solid">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM9 11v4h2V9H9v2zm0-6v2h2V5H9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-key">
-        <path d="M12.26 11.74L10 14H8v2H6v2l-2 2H0v-4l8.26-8.26a6 6 0 1 1 4 4zm4.86-4.62A3 3 0 0 0 15 2a3 3 0 0 0-2.12.88l4.24 4.24z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-keyboard">
-        <path d="M0 6c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm2 0v2h2V6H2zm1 3v2h2V9H3zm-1 3v2h2v-2H2zm3 0v2h10v-2H5zm11 0v2h2v-2h-2zM6 9v2h2V9H6zm3 0v2h2V9H9zm3 0v2h2V9h-2zm3 0v2h2V9h-2zM5 6v2h2V6H5zm3 0v2h2V6H8zm3 0v2h2V6h-2zm3 0v2h4V6h-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-layers">
-        <path d="M10 1l10 6-10 6L0 7l10-6zm6.67 10L20 13l-10 6-10-6 3.33-2L10 15l6.67-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-library">
-        <path d="M0 6l10-6 10 6v2H0V6zm0 12h20v2H0v-2zm2-2h16v2H2v-2zm0-8h4v8H2V8zm6 0h4v8H8V8zm6 0h4v8h-4V8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-light-bulb">
-        <path d="M7 13.33a7 7 0 1 1 6 0V16H7v-2.67zM7 17h6v1.5c0 .83-.67 1.5-1.5 1.5h-3A1.5 1.5 0 0 1 7 18.5V17zm2-5.1V14h2v-2.1a5 5 0 1 0-2 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-link">
-        <path d="M9.26 13a2 2 0 0 1 .01-2.01A3 3 0 0 0 9 5H5a3 3 0 0 0 0 6h.08a6.06 6.06 0 0 0 0 2H5A5 5 0 0 1 5 3h4a5 5 0 0 1 .26 10zm1.48-6a2 2 0 0 1-.01 2.01A3 3 0 0 0 11 15h4a3 3 0 0 0 0-6h-.08a6.06 6.06 0 0 0 0-2H15a5 5 0 0 1 0 10h-4a5 5 0 0 1-.26-10z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-list-add">
-        <path d="M15 9h-3v2h3v3h2v-3h3V9h-3V6h-2v3zM0 3h10v2H0V3zm0 8h10v2H0v-2zm0-4h10v2H0V7zm0 8h10v2H0v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-list-bullet">
-        <path d="M1 4h2v2H1V4zm4 0h14v2H5V4zM1 9h2v2H1V9zm4 0h14v2H5V9zm-4 5h2v2H1v-2zm4 0h14v2H5v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-load-balancer">
-        <path d="M17 12h-6v4h1v4H8v-4h1v-4H3v4h1v4H0v-4h1v-4a2 2 0 0 1 2-2h6V6H7V0h6v6h-2v4h6a2 2 0 0 1 2 2v4h1v4h-4v-4h1v-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location">
-        <path d="M10 20S3 10.87 3 7a7 7 0 1 1 14 0c0 3.87-7 13-7 13zm0-11a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location-current">
-        <path d="M0 0l20 8-8 4-2 8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location-food">
-        <path d="M18 11v7a2 2 0 0 1-4 0v-5h-2V3a3 3 0 0 1 3-3h3v11zM4 10a2 2 0 0 1-2-2V1a1 1 0 0 1 2 0v4h1V1a1 1 0 0 1 2 0v4h1V1a1 1 0 0 1 2 0v7a2 2 0 0 1-2 2v8a2 2 0 0 1-4 0v-8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location-gas-station">
-        <path d="M13 18h1v2H0v-2h1V2c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v16zM3 2v6h8V2H3zm10 8h1a2 2 0 0 1 2 2v3a1 1 0 0 0 2 0v-5l-2-2V6l-2-2 1-1 5 5v7a3 3 0 0 1-6 0v-3h-1v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location-hotel">
-        <path d="M2 12h18v6h-2v-2H2v2H0V2h2v10zm8-6h8a2 2 0 0 1 2 2v3H10V6zm-4 5a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location-marina">
-        <path d="M8 1.88V0h2v16h10l-4 4H2l-2-4h8v-2H0v-.26A24.03 24.03 0 0 0 8 1.88zM19.97 14H10v-.36A11.94 11.94 0 0 0 10 .36v-.2A16.01 16.01 0 0 1 19.97 14z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location-park">
-        <path d="M5.33 12.77A4 4 0 1 1 3 5.13V5a4 4 0 0 1 5.71-3.62 3.5 3.5 0 0 1 6.26 1.66 2.5 2.5 0 0 1 2 2.08 4 4 0 1 1-2.7 7.49A5.02 5.02 0 0 1 12 14.58V18l2 1v1H6v-1l2-1v-3l-2.67-2.23zM5 10l3 3v-3H5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location-restroom">
-        <path d="M12 16H9l2-4.5V9c0-1.1.9-2 2-2h2a2 2 0 0 1 2 2v2.5l2 4.5h-3v4h-4v-4zm-5-3h2V9a2 2 0 0 0-2-2H3a2 2 0 0 0-2 2v4h2v7h4v-7zM5 6a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm9 0a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-location-shopping">
-        <path d="M16 6v2h2l2 12H0L2 8h2V6a6 6 0 1 1 12 0zm-2 0a4 4 0 1 0-8 0v2h8V6zM4 10v2h2v-2H4zm10 0v2h2v-2h-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-lock-closed">
-        <path d="M4 8V6a6 6 0 1 1 12 0v2h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-8c0-1.1.9-2 2-2h1zm5 6.73V17h2v-2.27a2 2 0 1 0-2 0zM7 6v2h6V6a3 3 0 0 0-6 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-lock-open">
-        <path d="M4 8V6a6 6 0 1 1 12 0h-3v2h4a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-8c0-1.1.9-2 2-2h1zm5 6.73V17h2v-2.27a2 2 0 1 0-2 0zM7 6v2h6V6a3 3 0 0 0-6 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-map">
-        <path d="M0 0l6 4 8-4 6 4v16l-6-4-8 4-6-4V0zm7 6v11l6-3V3L7 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-menu">
-        <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-mic">
-        <path d="M9 18v-1.06A8 8 0 0 1 2 9h2a6 6 0 1 0 12 0h2a8 8 0 0 1-7 7.94V18h3v2H6v-2h3zM6 4a4 4 0 1 1 8 0v5a4 4 0 1 1-8 0V4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-minus-outline">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm5-9v2H5V9h10z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-minus-solid">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm5-11H5v2h10V9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-mobile-devices">
-        <path d="M17 6V5h-2V2H3v14h5v4h3.25H11a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6zm-5.75 14H3a2 2 0 0 1-2-2V2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v4a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-5.75zM11 8v8h6V8h-6zm3 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-mood-happy">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM6.5 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm7 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm2.16 3a6 6 0 0 1-11.32 0h11.32z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-mood-sad">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM6.5 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm7 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm2.16 6H4.34a6 6 0 0 1 11.32 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-mouse">
-        <path d="M4 9V6A6 6 0 0 1 9 .08V9H4zm0 2v3a6 6 0 1 0 12 0v-3H4zm12-2V6a6 6 0 0 0-5-5.92V9h5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-music-album">
-        <path d="M0 0h20v20H0V0zm10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-5a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-music-artist">
-        <path d="M15.75 8l-3.74-3.75a3.99 3.99 0 0 1 6.82-3.08A4 4 0 0 1 15.75 8zm-13.9 7.3l9.2-9.19 2.83 2.83-9.2 9.2-2.82-2.84zm-1.4 2.83l2.11-2.12 1.42 1.42-2.12 2.12-1.42-1.42zM10 15l2-2v7h-2v-5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-music-notes">
-        <path d="M20 2.5V0L6 2v12.17A3 3 0 0 0 5 14H3a3 3 0 0 0 0 6h2a3 3 0 0 0 3-3V5.71L18 4.3v7.88a3 3 0 0 0-1-.17h-2a3 3 0 0 0 0 6h2a3 3 0 0 0 3-3V2.5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-music-playlist">
-        <path d="M16 17a3 3 0 0 1-3 3h-2a3 3 0 0 1 0-6h2a3 3 0 0 1 1 .17V1l6-1v4l-4 .67V17zM0 3h12v2H0V3zm0 4h12v2H0V7zm0 4h12v2H0v-2zm0 4h6v2H0v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-navigation-more">
-        <path d="M4 12a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm6 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm6 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-network">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm7.75-8a8.01 8.01 0 0 0 0-4h-3.82a28.81 28.81 0 0 1 0 4h3.82zm-.82 2h-3.22a14.44 14.44 0 0 1-.95 3.51A8.03 8.03 0 0 0 16.93 14zm-8.85-2h3.84a24.61 24.61 0 0 0 0-4H8.08a24.61 24.61 0 0 0 0 4zm.25 2c.41 2.4 1.13 4 1.67 4s1.26-1.6 1.67-4H8.33zm-6.08-2h3.82a28.81 28.81 0 0 1 0-4H2.25a8.01 8.01 0 0 0 0 4zm.82 2a8.03 8.03 0 0 0 4.17 3.51c-.42-.96-.74-2.16-.95-3.51H3.07zm13.86-8a8.03 8.03 0 0 0-4.17-3.51c.42.96.74 2.16.95 3.51h3.22zm-8.6 0h3.34c-.41-2.4-1.13-4-1.67-4S8.74 3.6 8.33 6zM3.07 6h3.22c.2-1.35.53-2.55.95-3.51A8.03 8.03 0 0 0 3.07 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-news-paper">
-        <path d="M16 2h4v15a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V0h16v2zm0 2v13a1 1 0 0 0 1 1 1 1 0 0 0 1-1V4h-2zM2 2v15a1 1 0 0 0 1 1h11.17a2.98 2.98 0 0 1-.17-1V2H2zm2 8h8v2H4v-2zm0 4h8v2H4v-2zM4 4h8v4H4V4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-notifications">
-        <path d="M4 8a6 6 0 0 1 4.03-5.67 2 2 0 1 1 3.95 0A6 6 0 0 1 16 8v6l3 2v1H1v-1l3-2V8zm8 10a2 2 0 1 1-4 0h4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-notifications-outline">
-        <path d="M6 8v7h8V8a4 4 0 1 0-8 0zm2.03-5.67a2 2 0 1 1 3.95 0A6 6 0 0 1 16 8v6l3 2v1H1v-1l3-2V8a6 6 0 0 1 4.03-5.67zM12 18a2 2 0 1 1-4 0h4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-paste">
-        <path d="M10.5 20H2a2 2 0 0 1-2-2V6c0-1.1.9-2 2-2h1V3l2.03-.4a3 3 0 0 1 5.94 0L13 3v1h1a2 2 0 0 1 2 2v1h-2V6h-1v1H3V6H2v12h5v2h3.5zM8 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm2 4h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-8a2 2 0 0 1-2-2v-8c0-1.1.9-2 2-2zm0 2v8h8v-8h-8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-pause">
-        <path d="M5 4h3v12H5V4zm7 0h3v12h-3V4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-pause-outline">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM7 6h2v8H7V6zm4 0h2v8h-2V6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-pause-solid">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM7 6v8h2V6H7zm4 0v8h2V6h-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-pen-tool">
-        <path d="M11 9.27V0l6 11-4 6H7l-4-6L9 0v9.27a2 2 0 1 0 2 0zM6 18h8v2H6v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-phone">
-        <path d="M20 18.35V19a1 1 0 0 1-1 1h-2A17 17 0 0 1 0 3V1a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4c0 .56-.31 1.31-.7 1.7L3.16 8.84c1.52 3.6 4.4 6.48 8 8l2.12-2.12c.4-.4 1.15-.71 1.7-.71H19a1 1 0 0 1 .99 1v3.35z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-photo">
-        <path d="M0 4c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm11 9l-3-3-6 6h16l-5-5-2 2zm4-4a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-php-elephant">
-        <path fill-rule="evenodd"
-              d="M10 12v8A10 10 0 0 1 8.17.17L10 2h5a5 5 0 0 1 5 4.99v9.02A4 4 0 0 1 16 20v-2a2 2 0 1 0 0-4h-4l-2-2zm5.5-3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-pin">
-        <path d="M11 12h6v-1l-3-1V2l3-1V0H3v1l3 1v8l-3 1v1h6v7l1 1 1-1v-7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-play">
-        <path d="M4 4l12 6-12 6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-play-outline">
-        <path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM7 6l8 4-8 4V6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-plugin">
-        <path d="M20 14v4a2 2 0 0 1-2 2h-4v-2a2 2 0 0 0-2-2 2 2 0 0 0-2 2v2H6a2 2 0 0 1-2-2v-4H2a2 2 0 0 1-2-2 2 2 0 0 1 2-2h2V6c0-1.1.9-2 2-2h4V2a2 2 0 0 1 2-2 2 2 0 0 1 2 2v2h4a2 2 0 0 1 2 2v4h-2a2 2 0 0 0-2 2 2 2 0 0 0 2 2h2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-portfolio">
-        <path d="M9 12H1v6a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-6h-8v2H9v-2zm0-1H0V5c0-1.1.9-2 2-2h4V2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1h4a2 2 0 0 1 2 2v6h-9V9H9v2zm3-8V2H8v1h4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-printer">
-        <path d="M4 16H0V6h20v10h-4v4H4v-4zm2-4v6h8v-6H6zM4 0h12v5H4V0zM2 8v2h2V8H2zm4 0v2h2V8H6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-pylon">
-        <path d="M17.4 18H20v2H0v-2h2.6L8 0h4l5.4 18zm-3.2-4H5.8l-1.2 4h10.8l-1.2-4zm-2.4-8H8.2L7 10h6l-1.2-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-question">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm2-13c0 .28-.21.8-.42 1L10 9.58c-.57.58-1 1.6-1 2.42v1h2v-1c0-.29.21-.8.42-1L13 9.42c.57-.58 1-1.6 1-2.42a4 4 0 1 0-8 0h2a2 2 0 1 1 4 0zm-3 8v2h2v-2H9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-queue">
-        <path d="M0 2h20v4H0V2zm0 8h20v2H0v-2zm0 6h20v2H0v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-radar">
-        <path d="M12 10a2 2 0 0 1-3.41 1.41A2 2 0 0 1 10 8V0a9.97 9.97 0 0 1 10 10h-8zm7.9 1.41A10 10 0 1 1 8.59.1v2.03a8 8 0 1 0 9.29 9.29h2.02zm-4.07 0a6 6 0 1 1-7.25-7.25v2.1a3.99 3.99 0 0 0-1.4 6.57 4 4 0 0 0 6.56-1.42h2.1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-radio">
-        <path d="M20 9v9a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8c0-1.1.9-2 2-2h13.8L.74 1.97 1.26.03 20 5.06V9zm-5 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM2 8v2h16V8H2zm1.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm5 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm6.5-1a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-refresh">
-        <path d="M10 3v2a5 5 0 0 0-3.54 8.54l-1.41 1.41A7 7 0 0 1 10 3zm4.95 2.05A7 7 0 0 1 10 17v-2a5 5 0 0 0 3.54-8.54l1.41-1.41zM10 20l-4-4 4-4v8zm0-12V0l4 4-4 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-reload">
-        <path d="M14.66 15.66A8 8 0 1 1 17 10h-2a6 6 0 1 0-1.76 4.24l1.42 1.42zM12 10h8l-4 4-4-4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-reply">
-        <path d="M15 17v-2.99A4 4 0 0 0 11 10H8v5L2 9l6-6v5h3a6 6 0 0 1 6 6v3h-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-reply-all">
-        <path d="M18 17v-2.99A4 4 0 0 0 14 10h-3v5L5 9l6-6v5h3a6 6 0 0 1 6 6v3h-2zM6 6V3L0 9l6 6v-3L3 9l3-3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-repost">
-        <path d="M5 4a2 2 0 0 0-2 2v6H0l4 4 4-4H5V6h7l2-2H5zm10 4h-3l4-4 4 4h-3v6a2 2 0 0 1-2 2H6l2-2h7V8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-save-disk">
-        <path d="M0 2C0 .9.9 0 2 0h14l4 4v14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm5 0v6h10V2H5zm6 1h3v4h-3V3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-screen-full">
-        <path d="M2.8 15.8L0 13v7h7l-2.8-2.8 4.34-4.32-1.42-1.42L2.8 15.8zM17.2 4.2L20 7V0h-7l2.8 2.8-4.34 4.32 1.42 1.42L17.2 4.2zm-1.4 13L13 20h7v-7l-2.8 2.8-4.32-4.34-1.42 1.42 4.33 4.33zM4.2 2.8L7 0H0v7l2.8-2.8 4.32 4.34 1.42-1.42L4.2 2.8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-search">
-        <path d="M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-send">
-        <path d="M0 0l20 10L0 20V0zm0 8v4l10-2L0 8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-servers">
-        <path d="M0 2C0 .9.9 0 2 0h16a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm0 7c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V9zm0 7c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2zM12 2v2h2V2h-2zm4 0v2h2V2h-2zm-4 7v2h2V9h-2zm4 0v2h2V9h-2zm-4 7v2h2v-2h-2zm4 0v2h2v-2h-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-share">
-        <path d="M4 10c0-1.1.9-2 2-2h8c1.1 0 2 .9 2 2v8c0 1.1-.9 2-2 2H6c-1.1 0-2-.9-2-2v-8zm2 0v8h8v-8h-2V8H8v2H6zm3-6.17V16h2V3.83l3.07 3.07 1.42-1.41L10 0l-.7.7-4.8 4.8 1.42 1.4L9 3.84z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-share-alt">
-        <path d="M5.08 12.16A2.99 2.99 0 0 1 0 10a3 3 0 0 1 5.08-2.16l8.94-4.47a3 3 0 1 1 .9 1.79L5.98 9.63a3.03 3.03 0 0 1 0 .74l8.94 4.47A2.99 2.99 0 0 1 20 17a3 3 0 1 1-5.98-.37l-8.94-4.47z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-shield">
-        <path d="M19 11a7.5 7.5 0 0 1-3.5 5.94L10 20l-5.5-3.06A7.5 7.5 0 0 1 1 11V3c3.38 0 6.5-1.12 9-3 2.5 1.89 5.62 3 9 3v8zm-9 1.08l2.92 2.04-1.03-3.41 2.84-2.15-3.56-.08L10 5.12 8.83 8.48l-3.56.08L8.1 10.7l-1.03 3.4L10 12.09z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-shopping-cart">
-        <path d="M4 2h16l-3 9H4a1 1 0 1 0 0 2h13v2H4a3 3 0 0 1 0-6h.33L3 5 2 2H0V0h3a1 1 0 0 1 1 1v1zm1 18a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm10 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-show-sidebar">
-        <path d="M7 3H2v14h5V3zm2 0v14h9V3H9zM0 3c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm3 1h3v2H3V4zm0 3h3v2H3V7zm0 3h3v2H3v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-shuffle">
-        <path d="M6.59 12.83L4.4 15c-.58.58-1.59 1-2.4 1H0v-2h2c.29 0 .8-.2 1-.41l2.17-2.18 1.42 1.42zM16 4V1l4 4-4 4V6h-2c-.29 0-.8.2-1 .41l-2.17 2.18L9.4 7.17 11.6 5c.58-.58 1.59-1 2.41-1h2zm0 10v-3l4 4-4 4v-3h-2c-.82 0-1.83-.42-2.41-1l-8.6-8.59C2.8 6.21 2.3 6 2 6H0V4h2c.82 0 1.83.42 2.41 1l8.6 8.59c.2.2.7.41.99.41h2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-star-full">
-        <path d="M10 15l-5.878 3.09 1.123-6.545L.489 6.91l6.572-.955L10 0l2.939 5.955 6.572.955-4.756 4.635 1.123 6.545z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-station">
-        <path d="M9 11.73a2 2 0 1 1 2 0V20H9v-8.27zm5.24 2.51l-1.41-1.41A3.99 3.99 0 0 0 10 6a4 4 0 0 0-2.83 6.83l-1.41 1.41a6 6 0 1 1 8.49 0zm2.83 2.83l-1.41-1.41a8 8 0 1 0-11.31 0l-1.42 1.41a10 10 0 1 1 14.14 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-step-backward">
-        <path d="M4 5h3v10H4V5zm12 0v10l-9-5 9-5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-step-forward">
-        <path d="M13 5h3v10h-3V5zM4 5l9 5-9 5V5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-stethoscope">
-        <path d="M17 10.27V4.99a1 1 0 0 0-2 0V15a5 5 0 0 1-10 0v-1.08A6 6 0 0 1 0 8V2C0 .9.9 0 2 0h1a1 1 0 0 1 1 1 1 1 0 0 1-1 1H2v6a4 4 0 1 0 8 0V2H9a1 1 0 0 1-1-1 1 1 0 0 1 1-1h1a2 2 0 0 1 2 2v6a6 6 0 0 1-5 5.92V15a3 3 0 0 0 6 0V5a3 3 0 0 1 6 0v5.27a2 2 0 1 1-2 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-store-front">
-        <path d="M18 9.87V20H2V9.87a4.25 4.25 0 0 0 3-.38V14h10V9.5a4.26 4.26 0 0 0 3 .37zM3 0h4l-.67 6.03A3.43 3.43 0 0 1 3 9C1.34 9 .42 7.73.95 6.15L3 0zm5 0h4l.7 6.3c.17 1.5-.91 2.7-2.42 2.7h-.56A2.38 2.38 0 0 1 7.3 6.3L8 0zm5 0h4l2.05 6.15C19.58 7.73 18.65 9 17 9a3.42 3.42 0 0 1-3.33-2.97L13 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-stroke-width">
-        <path d="M0 0h20v5H0V0zm0 7h20v4H0V7zm0 6h20v3H0v-3zm0 5h20v2H0v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-subdirectory-left">
-        <path d="M18 12v1H8v5l-6-6 6-6v5h8V2h2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-subdirectory-right">
-        <path d="M3.5 13H12v5l6-6-6-6v5H4V2H2v11z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-swap">
-        <path d="M9 6a4 4 0 1 1 8 0v8h3l-4 4-4-4h3V6a2 2 0 0 0-2-2 2 2 0 0 0-2 2v8a4 4 0 1 1-8 0V6H0l4-4 4 4H5v8a2 2 0 0 0 2 2 2 2 0 0 0 2-2V6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-tablet">
-        <path d="M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm2 0v14h12V2H4zm6 17a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-tag">
-        <path d="M0 10V2l2-2h8l10 10-10 10L0 10zm4.5-4a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-target">
-        <path d="M17.94 11H13V9h4.94A8 8 0 0 0 11 2.06V7H9V2.06A8 8 0 0 0 2.06 9H7v2H2.06A8 8 0 0 0 9 17.94V13h2v4.94A8 8 0 0 0 17.94 11zM10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-text-box">
-        <path d="M0 0h6v6H0V0zm2 2v2h2V2H2zm12-2h6v6h-6V0zm2 2v2h2V2h-2zm-2 12h6v6h-6v-6zm2 2v2h2v-2h-2zM0 14h6v6H0v-6zm2 2v2h2v-2H2zM6 2h8v2H6V2zm0 14h8v2H6v-2zM16 6h2v8h-2V6zM2 6h2v8H2V6zm5 1h6v2H7V7zm2 2h2v4H9V9z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-text-decoration">
-        <path d="M12 5h-2v12H8V3h8v2h-2v12h-2V5zM8 3a4 4 0 1 0 0 8V3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-thermometer">
-        <path d="M9 11.17V7h2v4.17a3 3 0 1 1-2 0zm-1-.63a4 4 0 1 0 4 0V4a2 2 0 1 0-4 0v6.53zM6 9.53V4a4 4 0 0 1 8 0v5.53A5.99 5.99 0 0 1 10 20 6 6 0 0 1 6 9.53z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-thumbs-down">
-        <path d="M11 20a2 2 0 0 1-2-2v-6H2a2 2 0 0 1-2-2V8l2.3-6.12A3.11 3.11 0 0 1 5 0h8a2 2 0 0 1 2 2v8l-3 7v3h-1zm6-10V0h3v10h-3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-thumbs-up">
-        <path d="M11 0h1v3l3 7v8a2 2 0 0 1-2 2H5c-1.1 0-2.31-.84-2.7-1.88L0 12v-2a2 2 0 0 1 2-2h7V2a2 2 0 0 1 2-2zm6 10h3v10h-3V10z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-ticket">
-        <path d="M20 12v5H0v-5a2 2 0 1 0 0-4V3h20v5a2 2 0 1 0 0 4zM3 5v10h14V5H3zm7 7.08l-2.92 2.04L8.1 10.7 5.27 8.56l3.56-.08L10 5.12l1.17 3.36 3.56.08-2.84 2.15 1.03 3.4L10 12.09z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-time">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-1-7.59V4h2v5.59l3.95 3.95-1.41 1.41L9 10.41z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-timer">
-        <path d="M16.32 7.1A8 8 0 1 1 9 4.06V2h2v2.06c1.46.18 2.8.76 3.9 1.62l1.46-1.46 1.42 1.42-1.46 1.45zM10 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12zM7 0h6v2H7V0zm5.12 8.46l1.42 1.42L10 13.4 8.59 12l3.53-3.54z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-translate">
-        <path d="M7.41 9l2.24 2.24-.83 2L6 10.4l-3.3 3.3-1.4-1.42L4.58 9l-.88-.88c-.53-.53-1-1.3-1.3-2.12h2.2c.15.28.33.53.51.7l.89.9.88-.88C7.48 6.1 8 4.84 8 4H0V2h5V0h2v2h5v2h-2c0 1.37-.74 3.15-1.7 4.12L7.4 9zm3.84 8L10 20H8l5-12h2l5 12h-2l-1.25-3h-5.5zm.83-2h3.84L14 10.4 12.08 15z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-trash">
-        <path d="M6 2l2-2h4l2 2h4v2H2V2h4zM3 6h14l-1 14H4L3 6zm5 2v10h1V8H8zm3 0v10h1V8h-1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-travel-bus">
-        <path d="M13 18H7v1a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1a2 2 0 0 1-2-2V2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1zM4 5v6h5V5H4zm7 0v6h5V5h-5zM5 2v1h10V2H5zm.5 14a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm9 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-travel-car">
-        <path d="M2 14v-3H1a1 1 0 0 1-1-1 1 1 0 0 1 1-1h1l4-7h8l4 7h1a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-1v6a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1H5v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-3zm13.86-5L13 4H7L4.14 9h11.72zM5.5 14a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm9 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-travel-case">
-        <path d="M14 5h2v14H4V5h2V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1zm3 0h1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1V5zM3 5v14H2a2 2 0 0 1-2-2V7c0-1.1.9-2 2-2h1zm5-1v1h4V4H8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-travel-taxi-cab">
-        <path d="M12 3h2l4 7h1a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-1v6a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1H5v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-6H1a1 1 0 0 1-1-1 1 1 0 0 1 1-1h1l4-7h2V1h4v2zm3.86 7L13 5H7l-2.86 5h11.72zM5.5 15a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm9 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-travel-train">
-        <path d="M12 18H8l-2 2H3l2-2a2 2 0 0 1-2-2V2c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2l2 2h-3l-2-2zM5 5v6h10V5H5zm1.5 11a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm7 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM8 2v1h4V2H8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-travel-walk">
-        <path d="M11 7l1.44 2.16c.31.47 1.01.84 1.57.84H17V8h-3l-1.44-2.16a5.94 5.94 0 0 0-1.4-1.4l-1.32-.88a1.72 1.72 0 0 0-1.7-.04L4 6v5h2V7l2-1-3 14h2l2.35-7.65L11 14v6h2v-8l-2.7-2.7L11 7zm1-3a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-trophy">
-        <path d="M15 9a3 3 0 0 0 3-3h2a5 5 0 0 1-5.1 5 5 5 0 0 1-3.9 3.9V17l5 2v1H4v-1l5-2v-2.1A5 5 0 0 1 5.1 11H5a5 5 0 0 1-5-5h2a3 3 0 0 0 3 3V4H2v2H0V2h5V0h10v2h5v4h-2V4h-3v5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-tuning">
-        <path d="M17 16v4h-2v-4h-2v-3h6v3h-2zM1 9h6v3H1V9zm6-4h6v3H7V5zM3 0h2v8H3V0zm12 0h2v12h-2V0zM9 0h2v4H9V0zM3 12h2v8H3v-8zm6-4h2v12H9V8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-upload">
-        <path d="M13 10v6H7v-6H2l8-8 8 8h-5zM0 18h20v2H0v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-usb">
-        <path d="M15 8v2h-4V4h2l-3-4-3 4h2v8H5V9.73a2 2 0 1 0-2 0V12a2 2 0 0 0 2 2h4v2.27a2 2 0 1 0 2 0V12h4a2 2 0 0 0 2-2V8h1V4h-4v4h1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-user">
-        <path d="M5 5a5 5 0 0 1 10 0v2A5 5 0 0 1 5 7V5zM0 16.68A19.9 19.9 0 0 1 10 14c3.64 0 7.06.97 10 2.68V20H0v-3.32z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-user-add">
-        <path d="M2 6H0v2h2v2h2V8h2V6H4V4H2v2zm7 0a3 3 0 0 1 6 0v2a3 3 0 0 1-6 0V6zm11 9.14A15.93 15.93 0 0 0 12 13c-2.91 0-5.65.78-8 2.14V18h16v-2.86z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-user-group">
-        <path d="M7 8a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0 1c2.15 0 4.2.4 6.1 1.09L12 16h-1.25L10 20H4l-.75-4H2L.9 10.09A17.93 17.93 0 0 1 7 9zm8.31.17c1.32.18 2.59.48 3.8.92L18 16h-1.25L16 20h-3.96l.37-2h1.25l1.65-8.83zM13 0a4 4 0 1 1-1.33 7.76 5.96 5.96 0 0 0 0-7.52C12.1.1 12.53 0 13 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-user-solid-circle">
-        <path d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zM7 6v2a3 3 0 1 0 6 0V6a3 3 0 1 0-6 0zm-3.65 8.44a8 8 0 0 0 13.3 0 15.94 15.94 0 0 0-13.3 0z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-user-solid-square">
-        <path d="M0 2C0 .9.9 0 2 0h16a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm7 4v2a3 3 0 1 0 6 0V6a3 3 0 1 0-6 0zm11 9.14A15.93 15.93 0 0 0 10 13c-2.91 0-5.65.78-8 2.14V18h16v-2.86z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-vector">
-        <path d="M12 4h4.27a2 2 0 1 1 0 2h-2.14a9 9 0 0 1 4.84 7.25 2 2 0 1 1-2 .04 7 7 0 0 0-4.97-6V8H8v-.71a7 7 0 0 0-4.96 6 2 2 0 1 1-2-.04A9 9 0 0 1 5.86 6H3.73a2 2 0 1 1 0-2H8V3h4v1z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-video-camera">
-        <path d="M16 7l4-4v14l-4-4v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v3zm-8 7a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0-2a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-view-carousel">
-        <path d="M16 16v2H4v-2H0V4h4V2h12v2h4v12h-4zM14 5.5V4H6v12h8V5.5zm2 .5v8h2V6h-2zM4 6H2v8h2V6z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-view-column">
-        <path d="M12 4H8v12h4V4zm2 0v12h4V4h-4zM6 4H2v12h4V4zM0 2h20v16H0V2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-view-hide">
-        <path d="M12.81 4.36l-1.77 1.78a4 4 0 0 0-4.9 4.9l-2.76 2.75C2.06 12.79.96 11.49.2 10a11 11 0 0 1 12.6-5.64zm3.8 1.85c1.33 1 2.43 2.3 3.2 3.79a11 11 0 0 1-12.62 5.64l1.77-1.78a4 4 0 0 0 4.9-4.9l2.76-2.75zm-.25-3.99l1.42 1.42L3.64 17.78l-1.42-1.42L16.36 2.22z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-view-list">
-        <path d="M0 3h20v2H0V3zm0 4h20v2H0V7zm0 4h20v2H0v-2zm0 4h20v2H0v-2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-view-show">
-        <path d="M.2 10a11 11 0 0 1 19.6 0A11 11 0 0 1 .2 10zm9.8 4a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0-2a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-view-tile">
-        <path d="M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-volume-down">
-        <path d="M7 7H3v6h4l5 5V2L7 7zm8.54 6.54l-1.42-1.42a3 3 0 0 0 0-4.24l1.42-1.42a4.98 4.98 0 0 1 0 7.08z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-volume-mute">
-        <path d="M9 7H5v6h4l5 5V2L9 7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-volume-off">
-        <path d="M15 8.59l-2.12-2.13-1.42 1.42L13.6 10l-2.13 2.12 1.42 1.42L15 11.4l2.12 2.13 1.42-1.42L16.4 10l2.13-2.12-1.42-1.42L15 8.6zM4 7H0v6h4l5 5V2L4 7z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-volume-up">
-        <path d="M5 7H1v6h4l5 5V2L5 7zm11.36 9.36l-1.41-1.41a6.98 6.98 0 0 0 0-9.9l1.41-1.41a8.97 8.97 0 0 1 0 12.72zm-2.82-2.82l-1.42-1.42a3 3 0 0 0 0-4.24l1.42-1.42a4.98 4.98 0 0 1 0 7.08z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-wallet">
-        <path d="M0 4c0-1.1.9-2 2-2h15a1 1 0 0 1 1 1v1H2v1h17a1 1 0 0 1 1 1v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm16.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-watch">
-        <path d="M11 9h2v2H9V7h2v2zm-5.82 6.08a6.98 6.98 0 0 1 0-10.16L6 0h8l.82 4.92a6.98 6.98 0 0 1 0 10.16L14 20H6l-.82-4.92zM10 15a5 5 0 1 0 0-10 5 5 0 0 0 0 10z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-wrench">
-        <path d="M6.47 9.8A5 5 0 0 1 .2 3.22l3.95 3.95 2.82-2.83L3.03.41a5 5 0 0 1 6.4 6.68l10 10-2.83 2.83L6.47 9.8z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-zoom-in">
-        <path fill-rule="evenodd"
-              d="M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM7 7V5h2v2h2v2H9v2H7V9H5V7h2z"/>
-    </symbol>
-    <symbol viewBox="0 0 20 20" id="zondicon-zoom-out">
-        <path fill-rule="evenodd"
-              d="M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 7h6v2H5V7z"/>
-    </symbol>
-</svg>

+ 0 - 6
public/vendor/horizon/mix-manifest.json

@@ -1,6 +0,0 @@
-{
-    "/app.js": "/app.js?id=9db6ba6424a3d1048c194c9c1e4429fe",
-    "/app-dark.css": "/app-dark.css?id=ff172044c4efc9f08f12c0eb824b0226",
-    "/app.css": "/app.css?id=a38514598173eedd6b8575a77bc1ead4",
-    "/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f"
-}

+ 5 - 8
readme.md

@@ -14,17 +14,14 @@
 ## Document
 [Click](https://docs.v2board.com)
 
-## Donation
-[![Donate with Ethereum](https://en.cryptobadges.io/badge/big/0x41dea6134d6Bf4f78cD88C71666144315573c9d3?showBalance=true)](https://en.cryptobadges.io/donate/0x41dea6134d6Bf4f78cD88C71666144315573c9d3)
-
 ## Sponsors
 Thanks to the open source project license provided by [Jetbrains](https://www.jetbrains.com/)
 
 ## Community
 Telegram Channel: [@v2board](https://t.me/v2board)  
+Telegram Group: [@v2board_official](https://t.me/v2board_official)
 
-## Rules
-1.We have closed issue. If you want to find us, please contact us through the telegram group.  
-2.Forward, Please indicate the original repo.  
-3.This repo is v2board api interface open source.  
-4.Due to the copyright problem, we have no plan to open source the front-end, but the front-end code is still readable.
+## How to Feedback
+We have closed issues due to too much invalid feedback, you can give us your feedback in the following way.
+ - Contact us through Community
+ - Fork Dev branch commit pull request

+ 4 - 1
resources/lang/en-US.json

@@ -90,5 +90,8 @@
     "Request failed, please try again later": "Request failed, please try again later",
     "Register frequently, please try again after 1 hour": "Register frequently, please try again after 1 hour",
     "Uh-oh, we've had some problems, we're working on it.": "Uh-oh, we've had some problems, we're working on it",
-    "This subscription reset package does not apply to your subscription": "This subscription reset package does not apply to your subscription"
+    "This subscription reset package does not apply to your subscription": "This subscription reset package does not apply to your subscription",
+    "Login to :name": "Login to :name",
+    "Sending frequently, please try again later": "Sending frequently, please try again later",
+    "Current product is sold out": "Current product is sold out"
 }

+ 4 - 1
resources/lang/zh-CN.json

@@ -90,5 +90,8 @@
     "Request failed, please try again later": "请求失败,请稍后再试",
     "Register frequently, please try again after 1 hour": "注册频繁,请等待1小时后再次尝试",
     "Uh-oh, we've had some problems, we're working on it.": "遇到了些问题,我们正在进行处理",
-    "This subscription reset package does not apply to your subscription": "该订阅重置包不适用于你的订阅"
+    "This subscription reset package does not apply to your subscription": "该订阅重置包不适用于你的订阅",
+    "Login to :name": "登入到 :name",
+    "Sending frequently, please try again later": "发送频繁,请稍后再试",
+    "Current product is sold out": "当前商品已售罄"
 }

+ 195 - 0
resources/views/mail/classic/mailLogin.blade.php

@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+    <meta charset="UTF-8">
+    <title>邮箱验证码</title>
+    <style type="text/css">
+        img {
+            max-width: 100%;
+        }
+
+        body {
+            -webkit-font-smoothing: antialiased;
+            -webkit-text-size-adjust: none;
+            width: 100% !important;
+            height: 100%;
+            line-height: 1.6em;
+        }
+
+        body {
+            background-color: #f6f6f6;
+        }
+
+        @media only screen and (max-width: 640px) {
+            body {
+                padding: 0 !important;
+            }
+
+            h1 {
+                font-weight: 800 !important;
+                margin: 20px 0 5px !important;
+            }
+
+            h2 {
+                font-weight: 800 !important;
+                margin: 20px 0 5px !important;
+            }
+
+            h3 {
+                font-weight: 800 !important;
+                margin: 20px 0 5px !important;
+            }
+
+            h4 {
+                font-weight: 800 !important;
+                margin: 20px 0 5px !important;
+            }
+
+            h1 {
+                font-size: 22px !important;
+            }
+
+            h2 {
+                font-size: 18px !important;
+            }
+
+            h3 {
+                font-size: 16px !important;
+            }
+
+            .container {
+                padding: 0 !important;
+                width: 100% !important;
+            }
+
+            .content {
+                padding: 0 !important;
+            }
+
+            .content-wrap {
+                padding: 10px !important;
+            }
+
+            .invoice {
+                width: 100% !important;
+            }
+        }
+    </style>
+</head>
+
+<body itemscope itemtype="http://schema.org/EmailMessage"
+    style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
+    bgcolor="#f6f6f6">
+    <table class="body-wrap"
+        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;"
+        bgcolor="#f6f6f6">
+        <tr
+            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+            <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
+                valign="top">
+            </td>
+            <td class="container" width="600"
+                style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;"
+                valign="top">
+                <div class="content"
+                    style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
+                    <table class="main" width="100%" cellpadding="0" cellspacing="0"
+                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;"
+                        bgcolor="#fff">
+                        <tr
+                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                            <td class="alert alert-warning"
+                                style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 22px; font-weight: bold; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background-color: #0073ba; margin: 0; padding: 20px;"
+                                align="center" bgcolor="#0073ba" valign="top">
+                                {{$name}}
+                            </td>
+                        </tr>
+                        <tr
+                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                            <td class="content-wrap"
+                                style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;"
+                                valign="top">
+                                <table width="100%" cellpadding="0" cellspacing="0"
+                                    style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                    <tr
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                        <td class="content-block"
+                                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 34px; vertical-align: top; line-height: 1em; margin: 0; padding: 20px 0 30px;"
+                                            valign="top">
+                                            Dear Customer
+                                        </td>
+                                    </tr>
+                                    <tr
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                        <td class="content-block"
+                                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; color: #4a4a4a; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                            valign="top">
+                                            您正在登入到{{$name}}, 请在 5 分钟内点击下方链接进行登入。如果您未授权该登入请求,请无视。
+                                        </td>
+                                    </tr>
+                                    <tr
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                        <td class="content-block"
+                                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 36px; font-weight: bold; text-align: center; color: #4a4a4a; vertical-align: top; line-height: 1.6em; margin: 0; padding: 0 0 20px;"
+                                            valign="top">
+                                            <a href="{{$link}}">{{$link}}</a>
+                                        </td>
+                                    </tr>
+                                    <tr
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                        <td class="content-block"
+                                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #757575; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                            valign="top">
+                                            (本邮件由系统自动发出,请勿直接回复)
+                                        </td>
+                                    </tr>
+                                    <tr
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                        <td class="content-block"
+                                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; text-align: center; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                            valign="top">
+                                            <a href="{{$url}}"
+                                                class="btn-primary"
+                                                style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #fff; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #0073ba; margin: 0; border-color: #0073ba; border-style: solid; border-width: 8px 20px;">登录 {{$name}}</a>
+                                        </td>
+                                    </tr>
+                                </table>
+                            </td>
+                        </tr>
+                    </table>
+                    <div class="footer"
+                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
+                        <table width="100%"
+                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                            <tr
+                                style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                <td class="aligncenter content-block"
+                                    style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0;"
+                                    align="center" valign="top">
+                                    &copy; {{$name}}. All Rights Reserved.
+                                </td>
+                            </tr>
+                            <tr
+                                style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                <td class="aligncenter content-block"
+                                    style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;"
+                                    align="center" valign="top">
+                                    <a href="{{$url}}/#/subscribe"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">我的订阅</a> |
+                                    <a href="{{$url}}/#/knowledge"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">使用教程</a>
+                                </td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </td>
+            <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
+                valign="top">
+            </td>
+        </tr>
+    </table>
+</body>
+
+</html>

+ 2 - 2
resources/views/mail/classic/notify.blade.php

@@ -3,7 +3,7 @@
 
 <head>
     <meta charset="UTF-8">
-    <title>网站公告</title>
+    <title>网站通知</title>
     <style type="text/css">
         img {
             max-width: 100%;
@@ -102,7 +102,7 @@
                             <td class="alert alert-warning"
                                 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 22px; font-weight: bold; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background-color: #0073ba; margin: 0; padding: 20px;"
                                 align="center" bgcolor="#0073ba" valign="top">
-                                网站公告
+                                网站通知
                             </td>
                         </tr>
                         <tr

+ 43 - 0
resources/views/mail/default/mailLogin.blade.php

@@ -0,0 +1,43 @@
+<div style="background: #eee">
+    <table width="600" border="0" align="center" cellpadding="0" cellspacing="0">
+        <tbody>
+        <tr>
+            <td>
+                <div style="background:#fff">
+                    <table width="100%" border="0" cellspacing="0" cellpadding="0">
+                        <thead>
+                        <tr>
+                            <td valign="middle" style="padding-left:30px;background-color:#415A94;color:#fff;padding:20px 40px;font-size: 21px;">{{$name}}</td>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        <tr style="padding:40px 40px 0 40px;display:table-cell">
+                            <td style="font-size:24px;line-height:1.5;color:#000;margin-top:40px">登入到{{$name}}</td>
+                        </tr>
+                        <tr>
+                            <td style="font-size:14px;color:#333;padding:24px 40px 0 40px">
+                                尊敬的用户您好!
+                                <br />
+                                <br />
+                                您正在登入到{{$name}}, 请在 5 分钟内点击下方链接进行登入。如果您未授权该登入请求,请无视。
+                                <a href="{{$link}}">{{$link}}</a>
+                            </td>
+                        </tr>
+                        <tr style="padding:40px;display:table-cell">
+                        </tr>
+                        </tbody>
+                    </table>
+                </div>
+                <div>
+                    <table width="100%" border="0" cellspacing="0" cellpadding="0">
+                        <tbody>
+                        <tr>
+                            <td style="padding:20px 40px;font-size:12px;color:#999;line-height:20px;background:#f7f7f7"><a href="{{$url}}" style="font-size:14px;color:#929292">返回{{$name}}</a></td>
+                        </tr>
+                        </tbody>
+                    </table>
+                </div></td>
+        </tr>
+        </tbody>
+    </table>
+</div>

+ 1 - 1
resources/views/mail/default/notify.blade.php

@@ -12,7 +12,7 @@
                         </thead>
                         <tbody>
                         <tr style="padding:40px 40px 0 40px;display:table-cell">
-                            <td style="font-size:24px;line-height:1.5;color:#000;margin-top:40px">公告通知</td>
+                            <td style="font-size:24px;line-height:1.5;color:#000;margin-top:40px">网站通知</td>
                         </tr>
                         <tr>
                             <td style="font-size:14px;color:#333;padding:24px 40px 0 40px">

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است