初始化项目
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