From 7f5bd004a2747c2b306458d2756015b79164c8e6 Mon Sep 17 00:00:00 2001 From: "iVampireSP.com" Date: Sun, 14 May 2023 15:42:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChangePassword.php | 48 +++ app/Console/Commands/CreateAdmin.php | 62 ++++ app/Console/Commands/MakeAdmin.php | 56 --- app/Console/Kernel.php | 11 +- app/Helpers.php | 58 +++ .../Controllers/Admin/IndexController.php | 44 +++ .../Controllers/Admin/ServerController.php | 247 +++++++++++++ .../Controllers/Admin/TunnelController.php | 146 ++++++++ app/Http/Controllers/Admin/UserController.php | 66 ++++ .../Controllers/Api/PortManagerController.php | 25 +- app/Http/Controllers/Api/TunnelController.php | 11 +- app/Http/Middleware/Admin.php | 29 -- app/Jobs/CheckServer.php | 35 ++ app/Jobs/Cost.php | 151 ++++++++ app/Jobs/ServerCheckJob.php | 61 ++++ app/Jobs/StatusJob.php | 40 +++ app/Jobs/StopAllHostJob.php | 46 +++ app/Models/ActiveTunnel.php | 9 - app/Models/Admin.php | 1 - app/Models/Server.php | 8 +- app/Models/Tunnel.php | 56 +++ app/Providers/AppServiceProvider.php | 2 +- app/Providers/RouteServiceProvider.php | 6 + app/Support/Frp.php | 22 +- app/View/Components/AppLayout.php | 18 + app/View/Components/GuestLayout.php | 18 + app/View/Components/Menu.php | 28 ++ app/View/Components/ServerView.php | 33 ++ config/auth.php | 10 + .../2014_10_12_000000_create_users_table.php | 1 - .../2022_07_07_072505_create_admins_table.php | 36 ++ ..._02_10_135025_add_key_to_servers_table.php | 38 ++ ...2023_03_15_003244_create_tunnels_table.php | 5 + package.json | 2 + resources/js/components/Layout.vue | 3 - resources/js/components/Menu.vue | 28 +- resources/js/views/Tunnels/Index.vue | 120 ++++--- resources/js/views/Tunnels/Show.vue | 237 +++++++++++- resources/views/admin/index.blade.php | 10 + resources/views/admin/login.blade.php | 19 + .../views/admin/servers/create.blade.php | 134 +++++++ resources/views/admin/servers/edit.blade.php | 336 ++++++++++++++++++ resources/views/admin/servers/index.blade.php | 10 + resources/views/admin/servers/show.blade.php | 100 ++++++ resources/views/admin/tunnels/index.blade.php | 103 ++++++ resources/views/admin/tunnels/show.blade.php | 88 +++++ resources/views/admin/users/index.blade.php | 88 +++++ resources/views/components/menu.blade.php | 67 ++++ .../views/components/server-view.blade.php | 29 ++ resources/views/layouts/app.blade.php | 72 ++++ routes/admin.php | 27 ++ routes/api.php | 6 + yarn.lock | 25 ++ 53 files changed, 2721 insertions(+), 210 deletions(-) create mode 100644 app/Console/Commands/ChangePassword.php create mode 100644 app/Console/Commands/CreateAdmin.php delete mode 100644 app/Console/Commands/MakeAdmin.php create mode 100644 app/Helpers.php create mode 100644 app/Http/Controllers/Admin/IndexController.php create mode 100644 app/Http/Controllers/Admin/ServerController.php create mode 100644 app/Http/Controllers/Admin/TunnelController.php create mode 100644 app/Http/Controllers/Admin/UserController.php delete mode 100644 app/Http/Middleware/Admin.php create mode 100644 app/Jobs/CheckServer.php create mode 100644 app/Jobs/Cost.php create mode 100644 app/Jobs/ServerCheckJob.php create mode 100644 app/Jobs/StatusJob.php create mode 100644 app/Jobs/StopAllHostJob.php delete mode 100644 app/Models/ActiveTunnel.php create mode 100644 app/View/Components/AppLayout.php create mode 100644 app/View/Components/GuestLayout.php create mode 100644 app/View/Components/Menu.php create mode 100644 app/View/Components/ServerView.php create mode 100644 database/migrations/2022_07_07_072505_create_admins_table.php create mode 100644 database/migrations/2023_02_10_135025_add_key_to_servers_table.php create mode 100644 resources/views/admin/index.blade.php create mode 100644 resources/views/admin/login.blade.php create mode 100644 resources/views/admin/servers/create.blade.php create mode 100644 resources/views/admin/servers/edit.blade.php create mode 100644 resources/views/admin/servers/index.blade.php create mode 100644 resources/views/admin/servers/show.blade.php create mode 100644 resources/views/admin/tunnels/index.blade.php create mode 100644 resources/views/admin/tunnels/show.blade.php create mode 100644 resources/views/admin/users/index.blade.php create mode 100644 resources/views/components/menu.blade.php create mode 100644 resources/views/components/server-view.blade.php create mode 100644 resources/views/layouts/app.blade.php create mode 100644 routes/admin.php diff --git a/app/Console/Commands/ChangePassword.php b/app/Console/Commands/ChangePassword.php new file mode 100644 index 0000000..a64e355 --- /dev/null +++ b/app/Console/Commands/ChangePassword.php @@ -0,0 +1,48 @@ +ask('请输入邮箱'); + + $admin = Admin::where('email', $email)->first(); + if (! $admin) { + $this->error('用户不存在'); + + return 1; + } + + $password = $this->secret('请输入新密码'); + $admin->password = bcrypt($password); + $admin->save(); + + $this->info('密码修改成功'); + + return 0; + } +} diff --git a/app/Console/Commands/CreateAdmin.php b/app/Console/Commands/CreateAdmin.php new file mode 100644 index 0000000..17ef703 --- /dev/null +++ b/app/Console/Commands/CreateAdmin.php @@ -0,0 +1,62 @@ +info('由于是 local 环境,将会自动创建 Admin 用户。'); + Admin::create([ + 'name' => 'Test', + 'email' => 'im@ivampiresp.com', + 'password' => bcrypt('123456'), + ]); + $this->info('邮箱: im@ivampiresp.com, 密码: 123456'); + + return 0; + } + // ask for the name of the admin to create + $name = $this->ask('请输入用户名'); + // ask for the email of the admin to create + $email = $this->ask('请输入邮箱'); + + // enter password + $password = $this->secret('请输入密码(密码不会显示在终端)'); + + // create the admin + Admin::create([ + 'name' => $name, + 'email' => $email, + 'password' => bcrypt($password), + ]); + + $this->info('管理员创建成功!'); + + return 0; + } +} diff --git a/app/Console/Commands/MakeAdmin.php b/app/Console/Commands/MakeAdmin.php deleted file mode 100644 index 616b7fa..0000000 --- a/app/Console/Commands/MakeAdmin.php +++ /dev/null @@ -1,56 +0,0 @@ -argument('email'); - - $user = (new User)->where('email', $email)->first(); - - if ($user) { - - if (!$user->is_admin) { - $user->is_admin = true; - $user->save(); - $this->info('用户已经升级为管理员。需要取消管理员身份请使用再次执行此命令。'); - return; - } - - $cancel = $this->confirm('用户已经升级为管理员,要取消管理员身份吗?', true); - - if ($cancel) { - $user->is_admin = false; - $user->save(); - $this->info('用户已经取消管理员身份。'); - } else { - $this->info('用户仍然是管理员。'); - } - - } else { - $this->error('用户不存在。'); - } - } -} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 4c1dd5a..5e18e20 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -3,6 +3,7 @@ namespace App\Console; use Illuminate\Console\Scheduling\Schedule; +use App\Http\Controllers\Admin\ServerController; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel @@ -12,7 +13,15 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule): void { - // $schedule->command('inspire')->hourly(); + $schedule->call(function () { + (new ServerController())->checkServer(); + })->everyMinute()->name('FrpServer')->withoutOverlapping()->onOneServer(); + + // $schedule->job(new Cost())->hourly()->name('FrpServerCost')->withoutOverlapping()->onOneServer(); + + // every three days + // $schedule->job(new ReviewWebsiteJob())->daily()->name('reviewWebsiteJob')->withoutOverlapping()->onOneServer(); + } /** diff --git a/app/Helpers.php b/app/Helpers.php new file mode 100644 index 0000000..33d9603 --- /dev/null +++ b/app/Helpers.php @@ -0,0 +1,58 @@ +to($to)->with('status', $data ?? 'Success!'); + } + + return redirect()->back()->with('status', $data ?? 'Success!'); + } +} + +if (! function_exists('failed')) { + function failed($data = null) + { + return redirect()->back()->with('error', $data ?? 'Success!'); + } +} + +if (! function_exists('isUser')) { + function isUser($user_id) + { + return auth()->id() === $user_id ? true : false; + } +} + +if (! function_exists('unitConversion')) { + function unitConversion($num) + { + $p = 0; + $format = 'Bytes'; + if ($num > 0 && $num < 1024) { + $p = 0; + + return number_format($num).' '.$format; + } + if ($num >= 1024 && $num < pow(1024, 2)) { + $p = 1; + $format = 'KB'; + } + if ($num >= pow(1024, 2) && $num < pow(1024, 3)) { + $p = 2; + $format = 'MB'; + } + if ($num >= pow(1024, 3) && $num < pow(1024, 4)) { + $p = 3; + $format = 'GB'; + } + if ($num >= pow(1024, 4) && $num < pow(1024, 5)) { + $p = 3; + $format = 'TB'; + } + $num /= pow(1024, $p); + + return number_format($num, 3).' '.$format; + } +} diff --git a/app/Http/Controllers/Admin/IndexController.php b/app/Http/Controllers/Admin/IndexController.php new file mode 100644 index 0000000..21ec45f --- /dev/null +++ b/app/Http/Controllers/Admin/IndexController.php @@ -0,0 +1,44 @@ +check()) { + return view('admin.login'); + } else { + $servers = Server::where('status', '!=', 'up')->get(); + + return view('admin.index', compact('servers')); + } + } + + public function login(Request $request) + { + // attempt to login + if (Auth::guard('admin')->attempt($request->only(['email', 'password']), $request->has('remember'))) { + // if success, redirect to home + return redirect()->intended('/'); + } else { + // if fail, redirect to login with error message + return redirect()->back()->withErrors(['message' => '用户名或密码错误'])->withInput(); + } + } + + public function logout() + { + // logout + Auth::guard('admin')->logout(); + + return redirect()->route('admin.login'); + } +} diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php new file mode 100644 index 0000000..89f1bff --- /dev/null +++ b/app/Http/Controllers/Admin/ServerController.php @@ -0,0 +1,247 @@ +validate($this->rules()); + + $request_data = $request->toArray(); + // $request_data['user_id'] = auth()->id(); + + $request_data['allow_http'] = $request->allow_http ?? 0; + $request_data['allow_https'] = $request->allow_https ?? 0; + $request_data['allow_tcp'] = $request->allow_tcp ?? 0; + $request_data['allow_udp'] = $request->allow_udp ?? 0; + $request_data['allow_stcp'] = $request->allow_stcp ?? 0; + $request_data['allow_xtcp'] = $request->allow_xtcp ?? 0; + $request_data['allow_sudp'] = $request->allow_sudp ?? 0; + $request_data['is_china_mainland'] = $request->is_china_mainland ?? 0; + + $server = Server::create($request_data); + + return redirect()->route('admin.servers.edit', $server); + } + + /** + * Display the specified resource. + * + * @param Server $server + * @return RedirectResponse|View + */ + public function show(Server $server) + { + try { + $serverInfo = (object) (new Frp($server))->serverInfo(); + } catch (RequestException $e) { + Log::error($e->getMessage()); + + return redirect()->route('admin.servers.index')->with('error', '服务器连接失败。'); + } + + return view('admin.servers.show', compact('server')); + } + + /** + * Show the form for editing the specified resource. + * + * @param Server $server + * @return View + */ + public function edit(Server $server) + { + $serverInfo = (object) (new Frp($server))->serverInfo(); + + return view('admin.servers.edit', compact('server', 'serverInfo')); + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param Server $server + * @return RedirectResponse + */ + public function update(Request $request, Server $server) + { + if (! $request->has('status')) { + $request->merge(['allow_http' => $request->has('allow_http') ? true : false]); + $request->merge(['allow_https' => $request->has('allow_https') ? true : false]); + $request->merge(['allow_tcp' => $request->has('allow_tcp') ? true : false]); + $request->merge(['allow_udp' => $request->has('allow_udp') ? true : false]); + $request->merge(['allow_stcp' => $request->has('allow_stcp') ? true : false]); + $request->merge(['allow_xtcp' => $request->has('allow_xtcp') ? true : false]); + $request->merge(['allow_sudp' => $request->has('allow_sudp') ? true : false]); + $request->merge(['is_china_mainland' => $request->has('is_china_mainland') ? true : false]); + } + + $data = $request->all(); + + $server->update($data); + + return redirect()->route('admin.servers.index')->with('success', '服务器成功更新。'); + } + + /** + * Remove the specified resource from storage. + * + * @param Server $server + * @return RedirectResponse + */ + public function destroy(Server $server) + { + $server->delete(); + + return redirect()->route('admin.servers.index')->with('success', '服务器成功删除。'); + } + + public function rules($id = null) + { + return [ + 'name' => 'required|max:20', + 'server_address' => [ + 'required', + Rule::unique('servers')->ignore($id), + ], + 'server_port' => 'required|integer|max:65535|min:1', + 'token' => 'required|max:50', + 'dashboard_port' => 'required|integer|max:65535|min:1', + 'dashboard_user' => 'required|max:20', + 'dashboard_password' => 'required|max:32', + 'allow_http' => 'boolean', + 'allow_https' => 'boolean', + 'allow_tcp' => 'boolean', + 'allow_udp' => 'boolean', + 'allow_stcp' => 'boolean', + 'allow_xtcp' => 'boolean', + 'allow_sudp' => 'boolean', + 'min_port' => 'required|integer|max:65535|min:1', + 'max_port' => 'required|integer|max:65535|min:1', + 'max_tunnels' => 'required|integer|max:65535|min:1', + ]; + } + + public function checkServer($id = null) + { + if (is_null($id)) { + // refresh all + Server::chunk(100, function ($servers) { + foreach ($servers as $server) { + dispatch(new ServerCheckJob($server->id)); + } + }); + } else { + if (Server::where('id', $id)->exists()) { + dispatch(new ServerCheckJob($id)); + + return true; + } else { + return false; + } + } + } + + public function scanTunnel($server_id) + { + $server = Server::find($server_id); + if (is_null($server)) { + return false; + } + + $frp = new Frp($server); + + if ($server->allow_http) { + $proxies = $frp->httpTunnels()['proxies'] ?? ['proxies' => []]; + $this->cacheProxies($proxies); + } + + if ($server->allow_https) { + $proxies = $frp->httpsTunnels()['proxies'] ?? ['proxies' => []]; + $this->cacheProxies($proxies); + } + + if ($server->allow_tcp) { + $proxies = $frp->tcpTunnels()['proxies'] ?? ['proxies' => []]; + $this->cacheProxies($proxies); + } + + if ($server->allow_udp) { + $proxies = $frp->udpTunnels()['proxies'] ?? ['proxies' => []]; + $this->cacheProxies($proxies); + } + + if ($server->allow_stcp) { + $proxies = $frp->stcpTunnels()['proxies'] ?? ['proxies' => []]; + $this->cacheProxies($proxies); + } + + if ($server->allow_xtcp) { + $proxies = $frp->xtcpTunnels()['proxies'] ?? ['proxies' => []]; + $this->cacheProxies($proxies); + } + } + + private function cacheProxies($proxies) + { + foreach ($proxies as $proxy) { + if (! isset($proxy['name'])) { + continue; + } + + $cache_key = 'frpTunnel_data_'.$proxy['name']; + + Cache::put($cache_key, $proxy, 86400); + } + } + + public function getTunnel($name) + { + $cache_key = 'frpTunnel_data_'.$name; + + return Cache::get($cache_key); + } +} diff --git a/app/Http/Controllers/Admin/TunnelController.php b/app/Http/Controllers/Admin/TunnelController.php new file mode 100644 index 0000000..575fa1b --- /dev/null +++ b/app/Http/Controllers/Admin/TunnelController.php @@ -0,0 +1,146 @@ +has_free_traffic == 1) { + $hosts = $hosts->where('free_traffic', '>', 0); + } + + foreach ($request->except(['has_free_traffic', 'page']) as $key => $value) { + if (empty($value)) { + continue; + } + + if ($request->{$key}) { + $hosts = $hosts->where($key, 'LIKE', '%'.$value.'%'); + } + } + + $count = $hosts->count(); + + $hosts = $hosts->simplePaginate(100); + + return view('admin.tunnels.index', ['hosts' => $hosts, 'count' => $count]); + } + + /** + * Display the specified resource. + * + * @param Tunnel $tunnel + * @return View + */ + public function show(Tunnel $tunnel) + { + $tunnel->load('server'); + + return view('admin.tunnels.show', compact('tunnel')); + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param Tunnel $tunnel + * @return RedirectResponse + */ + public function update(Request $request, Tunnel $tunnel) + { + $request->validate([ + 'locked_reason' => 'nullable|string' + ]); + + $tunnel->update($request->all()); + + return back()->with('success', '完成。'); + } + + /** + * Remove the specified resource from storage. + * + * @param Tunnel $host + * @return RedirectResponse + */ + public function destroy(Tunnel $host) + { + $host->delete(); + + return back()->with('success', '已开始销毁。'); + } + + public function generateConfig(Tunnel $tunnel) + { + $tunnel->load('server'); + + // 配置文件 + $config = []; + + $config['server'] = <<server->server_address} +server_port = {$tunnel->server->server_port} +token = {$tunnel->server->token} +EOF; + + $local_addr = explode(':', $tunnel->local_address); + $config['client'] = <<client_token}] +type = {$tunnel->protocol} +local_ip = {$local_addr[0]} +local_port = {$local_addr[1]} +EOF; + + if ($tunnel->protocol == 'tcp' || $tunnel->protocol == 'udp') { + $config['client'] .= PHP_EOL . 'remote_port = ' . $tunnel->remote_port; + } elseif ($tunnel->protocol == 'http' || $tunnel->protocol == 'https') { + $config['client'] .= PHP_EOL . 'custom_domains = ' . $tunnel->custom_domain . PHP_EOL; + } elseif ($tunnel->server->allow_stcp || $tunnel->server->allow_xtcp || $tunnel->server->allow_sudp) { + $uuid = Str::uuid(); + $config['client'] .= <<sk} + +# 以下的是对端配置文件,请不要复制或者使用! +# 如果你想让别人通过 XTCP|STCP|SUDP 连接到你的主机,请将以下配置文件发给你信任的人。如果你不信任他人, 请勿发送, 这样会导致不信任的人也能通过 XTCP 连接到你的主机。 +# XTCP 连接不能保证稳定性, 并且也不会100%成功。 + + +#------ 对端复制开始 -------- +[common] +server_addr = {$tunnel->server->server_address} +server_port = {$tunnel->server->server_port} +user = visitor_{$uuid} +token = {$tunnel->server->token} +[lae_visitor_{$uuid}] +type = xtcp +role = visitor +server_name = lae_visitor_{$uuid} +sk = {$tunnel->sk} +bind_addr = {$local_addr[0]} +bind_port = {$local_addr[1]} +#------ 对端复制结束 -------- +# 非常感谢您的支持。如果您觉得这个项目不错,请将我们的网站分享给您的朋友。谢谢。 +EOF; + } + + return $config; + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..737fbc0 --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,66 @@ +except(['page']) as $key => $value) { + if (empty($value)) { + continue; + } + if ($request->{$key}) { + $users = $users->where($key, 'LIKE', '%'.$value.'%'); + } + } + + $count = $users->count(); + + $users = $users->simplePaginate(100); + + return view('admin.users.index', ['users' => $users, 'count' => $count]); + } + + public function update(Request $request, User $user) + { + $user->update($request->all()); + + // if not ajax + if ($request->ajax()) { + return response()->json([ + 'status' => 'success', + 'message' => '更新成功', + ]); + } else { + return redirect()->route('users.index'); + } + } + + /** + * Remove the specified resource from storage. + * + * @param User $user + * @return RedirectResponse + */ + public function destroy(User $user) + { + $user->delete(); + + return back()->with('success', '删除成功'); + } +} diff --git a/app/Http/Controllers/Api/PortManagerController.php b/app/Http/Controllers/Api/PortManagerController.php index cc7d5ae..34a7f93 100644 --- a/app/Http/Controllers/Api/PortManagerController.php +++ b/app/Http/Controllers/Api/PortManagerController.php @@ -40,15 +40,19 @@ public function handler(Request $request, $key) return $this->failed('找不到隧道。'); } - switch ($host->status) { - case 'stopped': - return $this->failed('隧道已停止。'); - case 'error': - return $this->failed('隧道出错。'); - case 'suspended': - return $this->failed('隧道已暂停。'); + if ($host->locked_reason) { + return $this->failed('隧道被锁定,原因是' . $host->locked_reason . '。'); } + // switch ($host->status) { + // case 'stopped': + // return $this->failed('隧道已停止。'); + // case 'error': + // return $this->failed('隧道出错。'); + // case 'suspended': + // return $this->failed('隧道已暂停。'); + // } + if ($request->input('content')['proxy_type'] !== $host->protocol) { return $this->failed('不允许的隧道协议。'); } @@ -69,14 +73,11 @@ public function handler(Request $request, $key) } // cache - // $cache_key = 'frp_user_' . $request->content['proxy_name']; - // Cache::put($cache_key, $host->user_id); - $cache_key = 'frpTunnel_data_' . $host->client_token; Cache::put($cache_key, ['status' => 'online']); - // $host->run_id = $request->input('content')['user']['run_id']; - // $host->saveQuietly(); + $host->run_id = $request->input('content')['user']['run_id']; + $host->saveQuietly(); // $data = [ // 'message' => '隧道 ' . $host->name . ' 已启动。', diff --git a/app/Http/Controllers/Api/TunnelController.php b/app/Http/Controllers/Api/TunnelController.php index 111369c..f2f1753 100644 --- a/app/Http/Controllers/Api/TunnelController.php +++ b/app/Http/Controllers/Api/TunnelController.php @@ -147,12 +147,19 @@ public function store(Request $request) /** * Display the specified resource. */ - public function show(TunnelRequest $request, Tunnel $tunnel) + public function show(TunnelRequest $tunnelRequest, Tunnel $tunnel) { - unset($request); + unset($tunnelRequest); + $tunnel['config'] = $tunnel->getConfig(); return $this->success($tunnel); } + public function close(TunnelRequest $tunnelRequest, Tunnel $tunnel) { + unset($tunnelRequest); + $tunnel->close(); + return $this->noContent(); + } + /** * Update the specified resource in storage. */ diff --git a/app/Http/Middleware/Admin.php b/app/Http/Middleware/Admin.php deleted file mode 100644 index a20b5fa..0000000 --- a/app/Http/Middleware/Admin.php +++ /dev/null @@ -1,29 +0,0 @@ -user('sanctum')?->isAdmin()) { - return response()->json([ - 'message' => 'You are not authorized to access this resource.' - ], 403); - } - - return $next($request); - } -} diff --git a/app/Jobs/CheckServer.php b/app/Jobs/CheckServer.php new file mode 100644 index 0000000..dc83f1c --- /dev/null +++ b/app/Jobs/CheckServer.php @@ -0,0 +1,35 @@ +http = Http::remote('remote')->asForm(); + + Server::with('hosts')->where('status', 'up')->whereNot('price_per_gb', 0)->chunk(100, function ($servers) { + foreach ($servers as $server) { + // $ServerCheckJob = new ServerCheckJob($server->id); + // $ServerCheckJob->handle(); + + foreach ($server->hosts as $host) { + $host->load('user'); + + Log::debug('------------'); + Log::debug('主机: ' . $host->name); + Log::debug('属于用户: ' . $host->user->name); + + $cache_key = 'frpTunnel_data_' . $host->client_token; + // $tunnel = 'frp_user_' . $host->client_token; + // $tunnel_user_id = Cache::get($tunnel); + $tunnel_data = Cache::get($cache_key, null); + + if (!is_null($tunnel_data)) { + $traffic = ($tunnel_data['today_traffic_in'] ?? 0) + ($tunnel_data['today_traffic_out'] ?? 0); + + // $traffic = 1073741824 * 10; + + Log::debug('本次使用的流量: ' . round($traffic / 1024 / 1024 / 1024, 2) ?? 0); + + $day = date('d'); + + $traffic_key = 'traffic_day_' . $day . '_used_' . $host->id; + + $used_traffic = Cache::get($traffic_key, 0); + if ($used_traffic !== $traffic) { + // 保存 2 天 + Cache::put($traffic_key, $traffic, 86400); + + $used_traffic_gb = round($used_traffic / 1024 / 1024 / 1024, 2); + + // Log::debug('上次使用的流量: ' . $used_traffic); + Log::debug('上次使用的流量 GB: ' . $used_traffic_gb); + + $used_traffic = $traffic - $used_traffic; + + Log::debug('流量差值: ' . round($used_traffic / 1024 / 1024 / 1024, 2) . ' GB'); + } + + $left_traffic = 0; + + if ($host->user->free_traffic > 0) { + Log::debug('开始扣除免费流量时的 used_traffic: ' . round($used_traffic / 1024 / 1024 / 1024, 2)); + + $user_free_traffic = round($host->user->free_traffic * 1024 * 1024 * 1024, 2); + + Log::debug('用户免费流量: ' . round($user_free_traffic / 1024 / 1024 / 1024, 2)); + + // $used_traffic -= $user_free_traffic; + // $used_traffic = abs($used_traffic); + + Log::debug('扣除免费流量时的 used_traffic: ' . $used_traffic / 1024 / 1024 / 1024); + + // 获取剩余 + $left_traffic = $user_free_traffic - $used_traffic; + + Log::debug('计算后剩余的免费流量: ' . $left_traffic / 1024 / 1024 / 1024); + + // 保存 + + if ($left_traffic < 0) { + $left_traffic = 0; + } + + // 保留两位小数 + $left_traffic = round($left_traffic / 1024 / 1024 / 1024, 2); + + $host->user->free_traffic = $left_traffic; + $host->user->save(); + } + + $used_traffic = abs($used_traffic); + + Log::debug('实际用量:' . $used_traffic / 1024 / 1024 / 1024); + + // $used_traffic -= $server->free_traffic * 1024 * 1024 * 1024; + // // $used_traffic = abs($used_traffic); + + // Log::debug('服务器免费流量: ' . $server->free_traffic * 1024 * 1024 * 1024); + + // Log::debug('使用的流量(减去服务器免费流量): ' . $used_traffic); + + if ($used_traffic > 0 && $left_traffic == 0) { + Log::debug('此时 used_traffic: ' . $used_traffic); + + // 要计费的流量 + $traffic = round($used_traffic / (1024 * 1024 * 1024), 2) ?? 0; + + $traffic = abs($traffic); + + $gb = round($traffic, 2); + + // 计算价格 + $cost = $traffic * $host->server->price_per_gb; + $cost = abs($cost); + + // 记录到日志 + // if local + // if (config('app.env') == 'local') { + Log::debug('计费:' . $host->server->name . ' ' . $host->name . ' ' . $gb . 'GB ' . $cost . ' 的 CNY 消耗'); + // } + + // 如果计费金额大于 0,则扣费 + if ($cost > 0) { + // 发送扣费请求 + $this->http->post('hosts/' . $host->host_id . '/cost', [ + 'amount' => $cost, + 'description' => $host->name . ' 的 ' . $gb . ' GB 流量费用。', + ]); + } + } + } + } + } + }); + } +} diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php new file mode 100644 index 0000000..54e35e0 --- /dev/null +++ b/app/Jobs/ServerCheckJob.php @@ -0,0 +1,61 @@ +id = $id; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $frpServer = Server::find($this->id); + if (!is_null($frpServer)) { + // $frp = new FrpController($this->id); + $s = new ServerController(); + $s->scanTunnel($frpServer->id); + $frpController = new Frp($frpServer); + $meta = $frpController->serverInfo(); + + if (!$meta) { + $meta = [ + 'status' => 'failed', + ]; + echo '服务器不可用: ' . $frpServer->name . ' failed' . PHP_EOL; + } else { + echo 'ServerCheckJob: ' . $frpServer->name . PHP_EOL; + } + + $data = $frpServer->toArray(); + $data['meta'] = $meta; + + Cache::put('serverinfo_' . $frpServer->id, $data, 300); + } + } +} diff --git a/app/Jobs/StatusJob.php b/app/Jobs/StatusJob.php new file mode 100644 index 0000000..a9652b2 --- /dev/null +++ b/app/Jobs/StatusJob.php @@ -0,0 +1,40 @@ +host_id = $host_id; + $this->requests = $requests; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + Http::remote()->asForm()->patch('hosts/'.$this->host_id, $this->requests); + } +} diff --git a/app/Jobs/StopAllHostJob.php b/app/Jobs/StopAllHostJob.php new file mode 100644 index 0000000..87b846c --- /dev/null +++ b/app/Jobs/StopAllHostJob.php @@ -0,0 +1,46 @@ +user_id = $user_id; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $hosts = Tunnel::where('user_id', $this->user_id); + + $hosts->chunk(100, function () use ($hosts) { + foreach ($hosts as $host) { + $host->status = 'stopped'; + $host->save(); + } + }); + } +} diff --git a/app/Models/ActiveTunnel.php b/app/Models/ActiveTunnel.php deleted file mode 100644 index f3cc9d9..0000000 --- a/app/Models/ActiveTunnel.php +++ /dev/null @@ -1,9 +0,0 @@ -belongsTo(Server::class); + } + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function getConfig() + { + return (new TunnelController)->generateConfig($this); + } + + public function close() + { + if ($this->run_id) { + $frp = new Frp($this->server); + $closed = $frp->close($this->run_id); + + if ($closed) { + $cache_key = 'frpTunnel_data_' . $this->client_token; + Cache::forget($cache_key); + + $this->run_id = null; + $this->saveQuietly(); + } + + return true; + } + + return false; + } + + + protected static function boot() + { + parent::boot(); + + static::creating(function (self $tunnel) { + $tunnel->client_token = Str::random(18); + }); + + static::updated(function (self $tunnel) { + if ($tunnel->locked_reason) { + $tunnel->close(); + } + }); + + static::deleted(function (self $tunnel) { + $tunnel->close(); + }); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..7e65199 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -11,7 +11,7 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // + require_once app_path() . '/Helpers.php'; } /** diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 4c26a19..ee104a9 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -31,8 +31,14 @@ public function boot(): void ->prefix('api') ->group(base_path('routes/api.php')); + Route::middleware(['web', 'auth:admin']) + ->as('admin.') + ->prefix('admin') + ->group(base_path('routes/admin.php')); + Route::middleware('web') ->group(base_path('routes/web.php')); + }); } diff --git a/app/Support/Frp.php b/app/Support/Frp.php index c013e16..ae7926a 100644 --- a/app/Support/Frp.php +++ b/app/Support/Frp.php @@ -11,12 +11,12 @@ class Frp { public string|int $id; - protected Server $frpServer; + protected Server $server; - public function __construct($id) + public function __construct(Server $server) { - $this->frpServer = (new Server)->find($id); - $this->id = $id; + $this->server = $server; + $this->id = $server->id; } public function serverInfo() @@ -48,22 +48,22 @@ protected function cache($key, $path = null) protected function get($url) { - $addr = 'http://' . $this->frpServer->server_address . ':' . $this->frpServer->dashboard_port . '/api' . $url; + $addr = 'http://' . $this->server->server_address . ':' . $this->server->dashboard_port . '/api' . $url; try { - $resp = Http::timeout(3)->withBasicAuth($this->frpServer->dashboard_user, $this->frpServer->dashboard_password)->get($addr)->json() ?? []; + $resp = Http::timeout(3)->withBasicAuth($this->server->dashboard_user, $this->server->dashboard_password)->get($addr)->json() ?? []; // if under maintenance - if ($this->frpServer->status !== 'maintenance') { - if ($this->frpServer->status !== 'up') { - $this->frpServer->status = 'up'; + if ($this->server->status !== 'maintenance') { + if ($this->server->status !== 'up') { + $this->server->status = 'up'; } } } catch (Exception) { - $this->frpServer->status = 'down'; + $this->server->status = 'down'; $resp = false; } finally { - $this->frpServer->save(); + $this->server->save(); } return $resp; diff --git a/app/View/Components/AppLayout.php b/app/View/Components/AppLayout.php new file mode 100644 index 0000000..b45d342 --- /dev/null +++ b/app/View/Components/AppLayout.php @@ -0,0 +1,18 @@ +server = $server; + $this->url = $url; + } + + /** + * Get the view / contents that represent the component. + * + * @return \Illuminate\Contracts\View\View|\Closure|string + */ + public function render() + { + return view('components.server-view'); + } +} diff --git a/config/auth.php b/config/auth.php index 9548c15..d0f51a7 100644 --- a/config/auth.php +++ b/config/auth.php @@ -40,6 +40,11 @@ 'driver' => 'session', 'provider' => 'users', ], + + 'admin' => [ + 'driver' => 'session', + 'provider' => 'admins', + ], ], /* @@ -65,6 +70,11 @@ 'model' => App\Models\User::class, ], + 'admins' => [ + 'driver' => 'eloquent', + 'model' => App\Models\Admin::class, + ], + // 'users' => [ // 'driver' => 'database', // 'table' => 'users', diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index c63c503..444fafb 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -17,7 +17,6 @@ public function up(): void $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); - $table->boolean('is_admin')->default(false)->index(); $table->rememberToken(); $table->timestamps(); }); diff --git a/database/migrations/2022_07_07_072505_create_admins_table.php b/database/migrations/2022_07_07_072505_create_admins_table.php new file mode 100644 index 0000000..1e7ab24 --- /dev/null +++ b/database/migrations/2022_07_07_072505_create_admins_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('admins'); + } +}; diff --git a/database/migrations/2023_02_10_135025_add_key_to_servers_table.php b/database/migrations/2023_02_10_135025_add_key_to_servers_table.php new file mode 100644 index 0000000..8deeb7b --- /dev/null +++ b/database/migrations/2023_02_10_135025_add_key_to_servers_table.php @@ -0,0 +1,38 @@ +string('key')->nullable()->index()->after('id'); + }); + + Server::all()->each(function (Server $server) { + $server->key = $server->id; + $server->save(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('key'); + }); + } +}; diff --git a/database/migrations/2023_03_15_003244_create_tunnels_table.php b/database/migrations/2023_03_15_003244_create_tunnels_table.php index 2d4e07c..75ad09b 100644 --- a/database/migrations/2023_03_15_003244_create_tunnels_table.php +++ b/database/migrations/2023_03_15_003244_create_tunnels_table.php @@ -16,6 +16,8 @@ public function up(): void $table->string('name')->index(); + $table->string('client_token')->index(); + $table->char('protocol', 5)->index()->default('tcp'); $table->string('custom_domain')->nullable()->index(); @@ -38,6 +40,9 @@ public function up(): void // use_compression $table->boolean('use_compression')->default(false)->index(); + $table->string('run_id')->nullable()->index(); + + $table->string('locked_reason')->nullable(); $table->timestamps(); }); diff --git a/package.json b/package.json index e2f790f..9316867 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "@vitejs/plugin-vue": "^4.0.0", "bootstrap": "5.3.0-alpha1", "bootstrap-icons": "^1.10.3", + "echarts": "^5.4.2", + "humanize-plus": "^1.8.2", "vue": "^3.2.36", "vue-axios": "^3.5.2", "vue-router": "^4.0.13" diff --git a/resources/js/components/Layout.vue b/resources/js/components/Layout.vue index 603bb8b..7fcbe87 100644 --- a/resources/js/components/Layout.vue +++ b/resources/js/components/Layout.vue @@ -8,9 +8,6 @@ -