初始化项目
from lae-modules-frp
This commit is contained in:
parent
ccf9eb7f73
commit
399b523893
56
app/Console/Commands/MakeAdmin.php
Normal file
56
app/Console/Commands/MakeAdmin.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class MakeAdmin extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'make-admin {email}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = '将一个用户升级为管理员,需要提供用户的邮箱。';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$email = $this->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('用户不存在。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
139
app/Helpers/ApiResponse.php
Normal file
139
app/Helpers/ApiResponse.php
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
trait ApiResponse
|
||||||
|
{
|
||||||
|
public function notFound($message = 'Not found'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// success
|
||||||
|
|
||||||
|
public function error($message = '', $code = 400): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->apiResponse(['message' => $message], $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// error
|
||||||
|
|
||||||
|
public function apiResponse($data, $status = 200): JsonResponse
|
||||||
|
{
|
||||||
|
if (is_string($data)) {
|
||||||
|
$data = ['message' => $data];
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($data, $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found
|
||||||
|
|
||||||
|
public function forbidden($message = 'Forbidden'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// forbidden
|
||||||
|
|
||||||
|
public function unauthorized($message = 'Unauthorized'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unauthorized
|
||||||
|
|
||||||
|
public function badRequest($message = 'Bad request'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad request
|
||||||
|
|
||||||
|
public function created($message = 'Created'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->success($message, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
// created
|
||||||
|
|
||||||
|
public function success($data = []): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->apiResponse($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// accepted
|
||||||
|
|
||||||
|
public function accepted($message = 'Accepted'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->success($message, 202);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no content
|
||||||
|
public function noContent($message = 'No content'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->success($message, 204);
|
||||||
|
}
|
||||||
|
|
||||||
|
// updated
|
||||||
|
public function updated($message = 'Updated'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->success($message, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleted
|
||||||
|
public function deleted($message = 'Deleted'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->success($message, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not allowed
|
||||||
|
public function notAllowed($message = 'Not allowed'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 405);
|
||||||
|
}
|
||||||
|
|
||||||
|
// conflict
|
||||||
|
public function conflict($message = 'Conflict'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 409);
|
||||||
|
}
|
||||||
|
|
||||||
|
// too many requests
|
||||||
|
public function tooManyRequests($message = 'Too many requests'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 429);
|
||||||
|
}
|
||||||
|
|
||||||
|
// server error
|
||||||
|
public function serverError($message = 'Server error'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// service unavailable
|
||||||
|
public function serviceUnavailable($message = 'Service unavailable'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
// method not allowed
|
||||||
|
public function methodNotAllowed($message = 'Method not allowed'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 405);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not acceptable
|
||||||
|
public function notAcceptable($message = 'Not acceptable'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 406);
|
||||||
|
}
|
||||||
|
|
||||||
|
// precondition failed
|
||||||
|
public function preconditionFailed($message = 'Precondition failed'): JsonResponse
|
||||||
|
{
|
||||||
|
return $this->error($message, 412);
|
||||||
|
}
|
||||||
|
}
|
109
app/Http/Controllers/Api/PortManagerController.php
Normal file
109
app/Http/Controllers/Api/PortManagerController.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Tunnel;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class PortManagerController extends Controller
|
||||||
|
{
|
||||||
|
public function handler(Request $request, $key)
|
||||||
|
{
|
||||||
|
if ($request->input('op') != 'NewProxy') {
|
||||||
|
return $this->failed('登录失败,请检查配置文件。');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($request->input('content')['user']['run_id'])) {
|
||||||
|
return $this->failed('此客户端不安全,我们不能让您登录。');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (!is_null($request->content['user']['user'])) {
|
||||||
|
// return $this->failed('用户不被允许。');
|
||||||
|
// }
|
||||||
|
|
||||||
|
$server = (new Server)->where('key', $key)->first();
|
||||||
|
|
||||||
|
if (is_null($server)) {
|
||||||
|
return $this->failed('服务器不存在。');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($server->status != 'up') {
|
||||||
|
return $this->failed('此服务器暂时不接受新的连接。');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search tunnel
|
||||||
|
$host = Tunnel::where('client_token', $request->input('content')['proxy_name'])->where('server_id', $server->id)->first();
|
||||||
|
if (is_null($host)) {
|
||||||
|
return $this->failed('找不到隧道。');
|
||||||
|
}
|
||||||
|
|
||||||
|
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('不允许的隧道协议。');
|
||||||
|
}
|
||||||
|
|
||||||
|
$test_protocol = 'allow_' . $request->input('content')['proxy_type'];
|
||||||
|
if (!$server->$test_protocol) {
|
||||||
|
return $this->failed('服务器不支持这个隧道协议。');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('content')['proxy_type'] == 'tcp' || $request->input('content')['proxy_type'] == 'udp') {
|
||||||
|
if ($request->input('content')['remote_port'] !== $host->remote_port || $host->remote_port < $server->min_port || $host->remote_port > $server->max_port) {
|
||||||
|
return $this->failed('拒绝启动隧道,因为端口不在允许范围内。');
|
||||||
|
}
|
||||||
|
} else if ($request->input('content')['proxy_type'] == 'http' || $request->input('content')['proxy_type'] == 'https') {
|
||||||
|
if ($request->input('content')['custom_domains'][0] != $host->custom_domain) {
|
||||||
|
return $this->failed('隧道配置文件有误。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// $data = [
|
||||||
|
// 'message' => '隧道 ' . $host->name . ' 已启动。',
|
||||||
|
// 'event' => 'notifications',
|
||||||
|
// ];
|
||||||
|
|
||||||
|
return $this->frpSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
// override
|
||||||
|
|
||||||
|
private function failed($reason = null)
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'reject' => true,
|
||||||
|
'reject_reason' => $reason ?? '隧道验证失败,请检查配置文件或前往这个网址重新配置隧道:' . config('app.url'),
|
||||||
|
'unchange' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function frpSuccess()
|
||||||
|
{
|
||||||
|
$response = [
|
||||||
|
'reject' => false,
|
||||||
|
'unchange' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->json($response);
|
||||||
|
}
|
||||||
|
}
|
123
app/Http/Controllers/Api/ServerController.php
Normal file
123
app/Http/Controllers/Api/ServerController.php
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ServerController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$servers = Server::all();
|
||||||
|
|
||||||
|
return $this->success($servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$this->abortIfNotAdmin();
|
||||||
|
|
||||||
|
$request->validate($this->rules());
|
||||||
|
|
||||||
|
$server = (new Server)->create([
|
||||||
|
'name' => $request->input('name'),
|
||||||
|
'server_address' => $request->input('server_address'),
|
||||||
|
'server_port' => $request->input('server_port'),
|
||||||
|
'token' => $request->input('token'),
|
||||||
|
'dashboard_port' => $request->input('dashboard_port'),
|
||||||
|
'dashboard_user' => $request->input('dashboard_user'),
|
||||||
|
'dashboard_password' => $request->input('dashboard_password'),
|
||||||
|
'allow_http' => $request->boolean('allow_http'),
|
||||||
|
'allow_https' => $request->boolean('allow_https'),
|
||||||
|
'allow_tcp' => $request->boolean('allow_tcp'),
|
||||||
|
'allow_udp' => $request->boolean('allow_udp'),
|
||||||
|
'allow_stcp' => $request->boolean('allow_stcp'),
|
||||||
|
'allow_sudp' => $request->boolean('allow_sudp'),
|
||||||
|
'allow_xtcp' => $request->boolean('allow_xtcp'),
|
||||||
|
'min_port' => $request->input('min_port'),
|
||||||
|
'max_port' => $request->input('max_port'),
|
||||||
|
'max_tunnels' => $request->input('max_tunnels'),
|
||||||
|
'is_china_mainland' => $request->boolean('is_china_mainland'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->created($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function abortIfNotAdmin()
|
||||||
|
{
|
||||||
|
if (!auth('sanctum')->user()?->isAdmin()) {
|
||||||
|
abort(403, 'You are not allowed to access this resource.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules($id = null)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'sometimes|max:20',
|
||||||
|
'server_address' => [
|
||||||
|
'sometimes',
|
||||||
|
Rule::unique('servers')->ignore($id),
|
||||||
|
],
|
||||||
|
'server_port' => 'sometimes|integer|max:65535|min:1',
|
||||||
|
'token' => 'sometimes|max:50',
|
||||||
|
'dashboard_port' => 'sometimes|integer|max:65535|min:1',
|
||||||
|
'dashboard_user' => 'sometimes|max:20',
|
||||||
|
'dashboard_password' => 'sometimes|max:30',
|
||||||
|
'allow_http' => 'nullable|boolean',
|
||||||
|
'allow_https' => 'nullable|boolean',
|
||||||
|
'allow_tcp' => 'nullable|boolean',
|
||||||
|
'allow_udp' => 'nullable|boolean',
|
||||||
|
'allow_stcp' => 'nullable|boolean',
|
||||||
|
'allow_sudp' => 'nullable|boolean',
|
||||||
|
'allow_xtcp' => 'nullable|boolean',
|
||||||
|
'min_port' => 'sometimes|integer|max:65535|min:1',
|
||||||
|
'max_port' => 'sometimes|integer|max:65535|min:1',
|
||||||
|
'max_tunnels' => 'sometimes|integer|max:65535|min:1',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(Request $request, Server $server)
|
||||||
|
{
|
||||||
|
if ($request->user('sanctum')->isAdmin()) {
|
||||||
|
$server->makeVisible($server->hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, Server $server)
|
||||||
|
{
|
||||||
|
$this->abortIfNotAdmin();
|
||||||
|
$request->validate($this->rules($server->id));
|
||||||
|
|
||||||
|
$server->update($request->all());
|
||||||
|
|
||||||
|
return $this->updated();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(Server $server)
|
||||||
|
{
|
||||||
|
$this->abortIfNotAdmin();
|
||||||
|
|
||||||
|
$server->delete();
|
||||||
|
}
|
||||||
|
}
|
51
app/Http/Controllers/Api/TunnelController.php
Normal file
51
app/Http/Controllers/Api/TunnelController.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TunnelController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
return $this->success(
|
||||||
|
$request->user()->tunnels()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
14
app/Http/Controllers/Api/UserController.php
Normal file
14
app/Http/Controllers/Api/UserController.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request)
|
||||||
|
{
|
||||||
|
return $this->success(auth('sanctum')->user());
|
||||||
|
}
|
||||||
|
}
|
29
app/Http/Middleware/Admin.php
Normal file
29
app/Http/Middleware/Admin.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class Admin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Closure(Request): (Response) $next
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (!$request->user('sanctum')?->isAdmin()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'You are not authorized to access this resource.'
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
9
app/Models/ActiveTunnel.php
Normal file
9
app/Models/ActiveTunnel.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ActiveTunnel extends Model
|
||||||
|
{
|
||||||
|
}
|
43
app/Models/Admin.php
Normal file
43
app/Models/Admin.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
|
class Admin extends Authenticatable
|
||||||
|
{
|
||||||
|
use HasApiTokens, Notifiable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
'remember_token',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'email_verified_at' => 'datetime',
|
||||||
|
];
|
||||||
|
}
|
51
app/Models/Server.php
Normal file
51
app/Models/Server.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class Server extends Model
|
||||||
|
{
|
||||||
|
public $hidden = [
|
||||||
|
'dashboard_password',
|
||||||
|
'dashboard_user',
|
||||||
|
'dashboard_port'
|
||||||
|
];
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'server_address',
|
||||||
|
'server_port',
|
||||||
|
'token',
|
||||||
|
'dashboard_port',
|
||||||
|
'dashboard_user',
|
||||||
|
'dashboard_password',
|
||||||
|
'allow_http',
|
||||||
|
'allow_https',
|
||||||
|
'allow_tcp',
|
||||||
|
'allow_udp',
|
||||||
|
'allow_stcp',
|
||||||
|
'allow_sudp',
|
||||||
|
'allow_xtcp',
|
||||||
|
'min_port',
|
||||||
|
'max_port',
|
||||||
|
'max_tunnels',
|
||||||
|
'is_china_mainland',
|
||||||
|
];
|
||||||
|
|
||||||
|
// tunnels
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::creating(function ($server) {
|
||||||
|
// $server->key = Str::random(32);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// on create
|
||||||
|
|
||||||
|
public function tunnels(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Tunnel::class);
|
||||||
|
}
|
||||||
|
}
|
27
app/Models/Tunnel.php
Normal file
27
app/Models/Tunnel.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Tunnel extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'protocol',
|
||||||
|
'custom_domain',
|
||||||
|
'local_address',
|
||||||
|
'remote_port',
|
||||||
|
'client_token',
|
||||||
|
'sk',
|
||||||
|
'status',
|
||||||
|
'server_id',
|
||||||
|
'user_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function server()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Server::class);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
111
app/Support/Frp.php
Normal file
111
app/Support/Frp.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class Frp
|
||||||
|
{
|
||||||
|
public string|int $id;
|
||||||
|
|
||||||
|
protected Server $frpServer;
|
||||||
|
|
||||||
|
public function __construct($id)
|
||||||
|
{
|
||||||
|
$this->frpServer = (new Server)->find($id);
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function serverInfo()
|
||||||
|
{
|
||||||
|
return $this->cache('serverinfo', '/serverinfo');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cache($key, $path = null)
|
||||||
|
{
|
||||||
|
$cache_key = 'frpTunnel_' . $this->id . '_' . $key;
|
||||||
|
if (Cache::has($cache_key)) {
|
||||||
|
return Cache::get($cache_key);
|
||||||
|
} else {
|
||||||
|
if ($path == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
$data = $this->get($path);
|
||||||
|
if (!$data) {
|
||||||
|
// request failed
|
||||||
|
Cache::put($cache_key, [], 10);
|
||||||
|
} else {
|
||||||
|
Cache::put($cache_key, $data, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function get($url)
|
||||||
|
{
|
||||||
|
$addr = 'http://' . $this->frpServer->server_address . ':' . $this->frpServer->dashboard_port . '/api' . $url;
|
||||||
|
try {
|
||||||
|
$resp = Http::timeout(3)->withBasicAuth($this->frpServer->dashboard_user, $this->frpServer->dashboard_password)->get($addr)->json() ?? [];
|
||||||
|
|
||||||
|
// if under maintenance
|
||||||
|
|
||||||
|
if ($this->frpServer->status !== 'maintenance') {
|
||||||
|
if ($this->frpServer->status !== 'up') {
|
||||||
|
$this->frpServer->status = 'up';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception) {
|
||||||
|
$this->frpServer->status = 'down';
|
||||||
|
$resp = false;
|
||||||
|
} finally {
|
||||||
|
$this->frpServer->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tcpTunnels()
|
||||||
|
{
|
||||||
|
return $this->cache('tcpTunnels', '/proxy/tcp');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function udpTunnels()
|
||||||
|
{
|
||||||
|
return $this->cache('udpTunnels', '/proxy/udp');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function httpTunnels()
|
||||||
|
{
|
||||||
|
return $this->cache('httpTunnels', '/proxy/http');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function httpsTunnels()
|
||||||
|
{
|
||||||
|
return $this->cache('httpsTunnels', '/proxy/https');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stcpTunnels()
|
||||||
|
{
|
||||||
|
return $this->cache('stcpTunnels', '/proxy/stcp');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function xtcpTunnels()
|
||||||
|
{
|
||||||
|
return $this->cache('stcpTunnels', '/proxy/xtcp');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function traffic($name)
|
||||||
|
{
|
||||||
|
return $this->cache('traffic_' . $name, '/traffic/' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close($run_id)
|
||||||
|
{
|
||||||
|
return $this->get('/client/close/' . $run_id);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('servers', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->string('name')->index();
|
||||||
|
|
||||||
|
$table->string('server_address')->index();
|
||||||
|
$table->string('server_port')->index();
|
||||||
|
$table->unsignedSmallInteger('dashboard_port');
|
||||||
|
$table->string('dashboard_user')->nullable();
|
||||||
|
$table->string('dashboard_password')->nullable();
|
||||||
|
|
||||||
|
$table->string('token')->nullable()->index();
|
||||||
|
|
||||||
|
$table->boolean('allow_http')->index()->default(true);
|
||||||
|
$table->boolean('allow_https')->index()->default(true);
|
||||||
|
$table->boolean('allow_tcp')->index()->default(true);
|
||||||
|
$table->boolean('allow_udp')->index()->default(true);
|
||||||
|
$table->boolean('allow_stcp')->index()->default(true);
|
||||||
|
$table->boolean('allow_sudp')->index()->default(true);
|
||||||
|
$table->boolean('allow_xtcp')->index()->default(true);
|
||||||
|
|
||||||
|
// bandwidth_limit
|
||||||
|
$table->unsignedBigInteger('bandwidth_limit')->default(0)->index();
|
||||||
|
|
||||||
|
$table->unsignedSmallInteger('min_port')->default(10000)->index();
|
||||||
|
$table->unsignedSmallInteger('max_port')->default(60000)->index();
|
||||||
|
|
||||||
|
$table->unsignedInteger('max_tunnels')->default(100)->index();
|
||||||
|
$table->unsignedInteger('tunnels')->default(0)->index();
|
||||||
|
|
||||||
|
$table->string('status')->default('maintenance');
|
||||||
|
|
||||||
|
// is_china_mainland
|
||||||
|
$table->boolean('is_china_mainland')->default(false)->index();
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('servers');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tunnels', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->string('name')->index();
|
||||||
|
|
||||||
|
$table->char('protocol', 5)->index()->default('tcp');
|
||||||
|
|
||||||
|
$table->string('custom_domain')->nullable()->index();
|
||||||
|
|
||||||
|
$table->string('local_address')->index();
|
||||||
|
|
||||||
|
$table->unsignedSmallInteger('remote_port')->index()->nullable();
|
||||||
|
|
||||||
|
$table->string('client_token')->index()->unique();
|
||||||
|
|
||||||
|
$table->string('sk')->index()->nullable();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('user_id')->index();
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('server_id')->index();
|
||||||
|
$table->foreign('server_id')->references('id')->on('servers')->cascadeOnDelete();
|
||||||
|
|
||||||
|
// use_encryption
|
||||||
|
$table->boolean('use_encryption')->default(false)->index();
|
||||||
|
|
||||||
|
// use_compression
|
||||||
|
$table->boolean('use_compression')->default(false)->index();
|
||||||
|
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('tunnels');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('active_tunnels', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
// 协议
|
||||||
|
$table->char('protocol', 5)->index()->default('tcp');
|
||||||
|
|
||||||
|
// 流量(全部 MB)
|
||||||
|
$table->unsignedBigInteger('traffic')->default(0)->index();
|
||||||
|
|
||||||
|
// 隧道名称
|
||||||
|
$table->string('name')->index();
|
||||||
|
|
||||||
|
// 记录正在运行的隧道
|
||||||
|
$table->unsignedBigInteger('tunnel_id')->index()->nullable();
|
||||||
|
$table->foreign('tunnel_id')->references('id')->on('tunnels')->cascadeOnDelete();
|
||||||
|
|
||||||
|
// 用户 ID
|
||||||
|
$table->unsignedBigInteger('user_id')->index()->nullable();
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
|
||||||
|
|
||||||
|
// run id
|
||||||
|
$table->string('run_id')->index()->nullable();
|
||||||
|
|
||||||
|
// 上次心跳
|
||||||
|
$table->timestamp('last_heartbeat_at')->nullable()->index();
|
||||||
|
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('active_tunnels');
|
||||||
|
}
|
||||||
|
};
|
0
resources/js/views/Servers.vue
Normal file
0
resources/js/views/Servers.vue
Normal file
299
resources/js/views/Servers/Create.vue
Normal file
299
resources/js/views/Servers/Create.vue
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>服务器</h3>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverName" class="form-label">服务器名称</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="serverName"
|
||||||
|
placeholder="输入服务器名称,他将会被搜索到"
|
||||||
|
name="name"
|
||||||
|
v-model="data.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Frps 信息</h3>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverAddr" class="form-label">Frps 地址</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="serverAddr"
|
||||||
|
name="server_address"
|
||||||
|
v-model="data.server_address"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverPort" class="form-label">Frps 端口</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="serverPort"
|
||||||
|
name="server_port"
|
||||||
|
v-model="data.server_port"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverToken" class="form-label">Frps 令牌</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="serverToken"
|
||||||
|
name="token"
|
||||||
|
v-model="data.token"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>服务器位置</h3>
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="is_china_mainland"
|
||||||
|
value="1"
|
||||||
|
id="is_china_mainland"
|
||||||
|
v-model="data.is_china_mainland"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="is_china_mainland">
|
||||||
|
在中国大陆
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<h3>Frps Dashboard 配置</h3>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="dashboardPort" class="form-label">端口</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="dashboardPort"
|
||||||
|
name="dashboard_port"
|
||||||
|
v-model="data.dashboard_port"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="dashboardUser" class="form-label">登录用户名</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="dashboardUser"
|
||||||
|
name="dashboard_user"
|
||||||
|
v-model="data.dashboard_user"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="dashboardPwd" class="form-label">密码</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="dashboardPwd"
|
||||||
|
name="dashboard_password"
|
||||||
|
v-model="data.dashboard_password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>端口范围限制</h3>
|
||||||
|
<div class="input-group input-group-sm mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
placeholder="最小端口,比如:1024"
|
||||||
|
name="min_port"
|
||||||
|
v-model="data.min_port"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
placeholder="最大端口,比如:65535"
|
||||||
|
name="max_port"
|
||||||
|
v-model="data.max_port"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>最多隧道数量</h3>
|
||||||
|
<div class="input-group input-group-sm mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
placeholder="最多隧道数量,比如:1024个隧道"
|
||||||
|
name="max_tunnels"
|
||||||
|
v-model="data.max_tunnels"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>隧道协议限制</h3>
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_http"
|
||||||
|
value="1"
|
||||||
|
id="allow_http"
|
||||||
|
v-model="data.allow_http"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_http">
|
||||||
|
允许 HTTP
|
||||||
|
</label>
|
||||||
|
<br />
|
||||||
|
超文本传输协议
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_https"
|
||||||
|
value="1"
|
||||||
|
id="allow_https"
|
||||||
|
v-model="data.allow_https"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_https">
|
||||||
|
允许 HTTPS
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_tcp"
|
||||||
|
value="1"
|
||||||
|
id="allow_tcp"
|
||||||
|
v-model="data.allow_tcp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_tcp">
|
||||||
|
允许 TCP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_udp"
|
||||||
|
value="1"
|
||||||
|
id="allow_udp"
|
||||||
|
v-model="data.allow_udp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_udp">
|
||||||
|
允许 UDP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_stcp"
|
||||||
|
value="1"
|
||||||
|
id="allow_stcp"
|
||||||
|
v-model="data.allow_stcp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_stcp">
|
||||||
|
允许 STCP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_sudp"
|
||||||
|
value="1"
|
||||||
|
id="allow_sudp"
|
||||||
|
v-model="data.allow_sudp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_sudp">
|
||||||
|
允许 SUDP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_xtcp"
|
||||||
|
value="1"
|
||||||
|
id="allow_xtcp"
|
||||||
|
v-model="data.allow_xtcp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_xtcp">
|
||||||
|
允许 XTCP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary mb-3"
|
||||||
|
@click.prevent="submit"
|
||||||
|
>
|
||||||
|
新建服务器
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import http from "../../plugins/http";
|
||||||
|
|
||||||
|
const data = ref({
|
||||||
|
name: "",
|
||||||
|
server_address: "",
|
||||||
|
token: "",
|
||||||
|
dashboard_port: "",
|
||||||
|
dashboard_user: "",
|
||||||
|
dashboard_password: "",
|
||||||
|
min_port: "",
|
||||||
|
max_port: "",
|
||||||
|
max_tunnels: "",
|
||||||
|
allow_http: "",
|
||||||
|
allow_https: "",
|
||||||
|
allow_tcp: "",
|
||||||
|
allow_udp: "",
|
||||||
|
allow_stcp: "",
|
||||||
|
allow_sudp: "",
|
||||||
|
allow_xtcp: "",
|
||||||
|
is_china_mainland: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
http.post("/servers", data.value).then((res) => {
|
||||||
|
if (res.status === 200 || res.status === 201) {
|
||||||
|
alert("服务器创建成功。");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据页面查询参数自动填充表单
|
||||||
|
const fillForm = () => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
for (const [key, value] of urlParams.entries()) {
|
||||||
|
// push to data
|
||||||
|
data.value[key] = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fillForm();
|
||||||
|
</script>
|
311
resources/js/views/Servers/Edit.vue
Normal file
311
resources/js/views/Servers/Edit.vue
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverName" class="form-label">服务器名称</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="serverName"
|
||||||
|
placeholder="输入服务器名称,它将会被搜索到"
|
||||||
|
name="name"
|
||||||
|
v-model="data.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Frps 信息</h3>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverAddr" class="form-label">Frps 地址</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="serverAddr"
|
||||||
|
name="server_address"
|
||||||
|
v-model="data.server_address"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverPort" class="form-label">Frps 端口</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="serverPort"
|
||||||
|
name="server_port"
|
||||||
|
v-model="data.server_port"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverToken" class="form-label">Frps 令牌</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="serverToken"
|
||||||
|
name="token"
|
||||||
|
v-model="data.token"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>服务器位置</h3>
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="is_china_mainland"
|
||||||
|
value="1"
|
||||||
|
id="is_china_mainland"
|
||||||
|
v-model="data.is_china_mainland"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="is_china_mainland">
|
||||||
|
在中国大陆
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<h3>Frps Dashboard 配置</h3>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="dashboardPort" class="form-label">端口</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="dashboardPort"
|
||||||
|
name="dashboard_port"
|
||||||
|
v-model="data.dashboard_port"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="dashboardUser" class="form-label">登录用户名</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="dashboardUser"
|
||||||
|
name="dashboard_user"
|
||||||
|
v-model="data.dashboard_user"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="dashboardPwd" class="form-label">密码</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
id="dashboardPwd"
|
||||||
|
name="dashboard_password"
|
||||||
|
v-model="data.dashboard_password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>端口范围限制</h3>
|
||||||
|
<div class="input-group input-group-sm mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
placeholder="最小端口,比如:1024"
|
||||||
|
name="min_port"
|
||||||
|
v-model="data.min_port"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
placeholder="最大端口,比如:65535"
|
||||||
|
name="max_port"
|
||||||
|
v-model="data.max_port"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>最多隧道数量</h3>
|
||||||
|
<div class="input-group input-group-sm mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
placeholder="最多隧道数量,比如:1024个隧道"
|
||||||
|
name="max_tunnels"
|
||||||
|
v-model="data.max_tunnels"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>隧道协议限制</h3>
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_http"
|
||||||
|
value="1"
|
||||||
|
id="allow_http"
|
||||||
|
v-model="data.allow_http"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_http">
|
||||||
|
允许 HTTP
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
超文本传输协议
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_https"
|
||||||
|
value="1"
|
||||||
|
id="allow_https"
|
||||||
|
v-model="data.allow_https"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_https">
|
||||||
|
允许 HTTPS
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_tcp"
|
||||||
|
value="1"
|
||||||
|
id="allow_tcp"
|
||||||
|
v-model="data.allow_tcp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_tcp">
|
||||||
|
允许 TCP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_udp"
|
||||||
|
value="1"
|
||||||
|
id="allow_udp"
|
||||||
|
v-model="data.allow_udp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_udp">
|
||||||
|
允许 UDP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_stcp"
|
||||||
|
value="1"
|
||||||
|
id="allow_stcp"
|
||||||
|
v-model="data.allow_stcp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_stcp">
|
||||||
|
允许 STCP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_sudp"
|
||||||
|
value="1"
|
||||||
|
id="allow_sudp"
|
||||||
|
v-model="data.allow_sudp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_sudp">
|
||||||
|
允许 SUDP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="allow_xtcp"
|
||||||
|
value="1"
|
||||||
|
id="allow_xtcp"
|
||||||
|
v-model="data.allow_xtcp"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="allow_xtcp">
|
||||||
|
允许 XTCP
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto mt-3">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary mb-3"
|
||||||
|
@click.prevent="submit"
|
||||||
|
>
|
||||||
|
修改服务器配置
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-danger mb-3"
|
||||||
|
style="margin-left: 5px "
|
||||||
|
@click.prevent="destroy"
|
||||||
|
>
|
||||||
|
删除服务器
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref} from 'vue'
|
||||||
|
import http from '../../plugins/http'
|
||||||
|
import route from '../../plugins/router'
|
||||||
|
|
||||||
|
const data = ref({})
|
||||||
|
let origin = {}
|
||||||
|
|
||||||
|
|
||||||
|
http.get('servers/' + route.currentRoute.value.params.id).then((res) => {
|
||||||
|
data.value = res.data
|
||||||
|
origin = JSON.parse(JSON.stringify(res.data))
|
||||||
|
})
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
// get changes
|
||||||
|
let changes = {}
|
||||||
|
|
||||||
|
for (let key in data.value) {
|
||||||
|
if (data.value[key] !== origin[key]) {
|
||||||
|
changes[key] = data.value[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.patch('servers/' + route.currentRoute.value.params.id, changes).then((res) => {
|
||||||
|
console.log(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
if (!confirm("确定删除服务器吗?")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
http.delete('servers/' + route.currentRoute.value.params.id).then(res => {
|
||||||
|
if (res.status === 200 || res.status === 204) {
|
||||||
|
alert("删除成功。")
|
||||||
|
|
||||||
|
route.push({
|
||||||
|
name: 'servers'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
69
resources/js/views/Servers/Index.vue
Normal file
69
resources/js/views/Servers/Index.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3>服务器列表</h3>
|
||||||
|
|
||||||
|
<div class="list-group">
|
||||||
|
|
||||||
|
<template v-for="server in servers">
|
||||||
|
<router-link :to="{
|
||||||
|
name: 'servers.edit',
|
||||||
|
params: {
|
||||||
|
id: server.id
|
||||||
|
}
|
||||||
|
}" class="list-group-item list-group-item-action" aria-current="true">
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1">{{ server.name }}</h5>
|
||||||
|
<small>{{ server.status }}</small>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">
|
||||||
|
<span class="badge bg-primary" v-show="server.allow_http">HTTP</span>
|
||||||
|
<span class="badge bg-primary" v-show="server.allow_https">HTTPS</span>
|
||||||
|
<span class="badge bg-primary" v-show="server.allow_tcp">TCP</span>
|
||||||
|
<span class="badge bg-primary" v-show="server.allow_udp">UDP</span>
|
||||||
|
<span class="badge bg-primary" v-show="server.allow_stcp">STCP</span>
|
||||||
|
<span class="badge bg-primary" v-show="server.allow_sudp">SUDP</span>
|
||||||
|
<span class="badge bg-primary" v-show="server.allow_xtcp">XTCP</span>
|
||||||
|
</p>
|
||||||
|
<small>{{ server.server_address }}:{{ server.server_port }}</small>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref} from 'vue'
|
||||||
|
import http from '../../plugins/http'
|
||||||
|
|
||||||
|
|
||||||
|
const servers = ref([{
|
||||||
|
"id": "1",
|
||||||
|
"name": "",
|
||||||
|
"server_address": "",
|
||||||
|
"server_port": "",
|
||||||
|
"token": "",
|
||||||
|
"allow_http": 1,
|
||||||
|
"allow_https": 0,
|
||||||
|
"allow_tcp": 0,
|
||||||
|
"allow_udp": 0,
|
||||||
|
"allow_stcp": 0,
|
||||||
|
"allow_sudp": 0,
|
||||||
|
"allow_xtcp": 0,
|
||||||
|
"bandwidth_limit": 0,
|
||||||
|
"min_port": 0,
|
||||||
|
"max_port": 1024,
|
||||||
|
"max_tunnels": 100,
|
||||||
|
"tunnels": 0,
|
||||||
|
"status": "maintenance",
|
||||||
|
"is_china_mainland": 0,
|
||||||
|
"created_at": "2023-03-15T11:57:47.000000Z",
|
||||||
|
"updated_at": "2023-03-15T11:57:47.000000Z"
|
||||||
|
}])
|
||||||
|
|
||||||
|
http.get('servers').then((res) => {
|
||||||
|
servers.value = res.data
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
3
resources/js/views/Tunnels/Active.vue
Normal file
3
resources/js/views/Tunnels/Active.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<h1>您名下的隧道</h1>
|
||||||
|
</template>
|
3
resources/js/views/Tunnels/Create.vue
Normal file
3
resources/js/views/Tunnels/Create.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<h3>创建隧道</h3>
|
||||||
|
</template>
|
55
resources/js/views/Tunnels/Index.vue
Normal file
55
resources/js/views/Tunnels/Index.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<h3>隧道列表</h3>
|
||||||
|
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">名称</th>
|
||||||
|
<th scope="col">协议</th>
|
||||||
|
<th scope="col">本地地址</th>
|
||||||
|
<th scope="col">远程端口/域名</th>
|
||||||
|
<th scope="col">连接数</th>
|
||||||
|
<th scope="col">下载流量</th>
|
||||||
|
<th scope="col">上载流量</th>
|
||||||
|
<th scope="col">服务器</th>
|
||||||
|
<th scope="col">状态</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>1</th>
|
||||||
|
<td><a href="http://portio.test/tunnels/1/edit">stcp</a></td>
|
||||||
|
<td>STCP</td>
|
||||||
|
<td>127.0.0.1:80</td>
|
||||||
|
|
||||||
|
<td>127.0.01:</td>
|
||||||
|
|
||||||
|
<td>0</td>
|
||||||
|
<td>0.000 Bytes</td>
|
||||||
|
<td>0.000 Bytes</td>
|
||||||
|
|
||||||
|
<td><a href="http://portio.test/servers/1">Test</a></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="text-danger">离线</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import http from "../../plugins/http";
|
||||||
|
|
||||||
|
|
||||||
|
const tunnels = ref([])
|
||||||
|
|
||||||
|
http.get('tunnels').then((res) => {
|
||||||
|
tunnels.value = res.data
|
||||||
|
|
||||||
|
console.log(tunnels.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user