mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-03 10:30:51 +08:00
feat: node traffic limit & batch operations
- Traffic monitoring with transfer_enable limit - Batch delete nodes - Reset traffic (single/batch)
This commit is contained in:
@@ -111,6 +111,94 @@ class ManageController extends Controller
|
|||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除节点
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function batchDelete(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'ids' => 'required|array',
|
||||||
|
'ids.*' => 'integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ids = $request->input('ids');
|
||||||
|
if (empty($ids)) {
|
||||||
|
return $this->fail([400, '请选择要删除的节点']);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$deleted = Server::whereIn('id', $ids)->delete();
|
||||||
|
if ($deleted === false) {
|
||||||
|
return $this->fail([500, '批量删除失败']);
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error($e);
|
||||||
|
return $this->fail([500, '批量删除失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置节点流量
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function resetTraffic(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = Server::find($request->id);
|
||||||
|
if (!$server) {
|
||||||
|
return $this->fail([400202, '服务器不存在']);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$server->u = 0;
|
||||||
|
$server->d = 0;
|
||||||
|
$server->save();
|
||||||
|
|
||||||
|
Log::info("Server {$server->id} ({$server->name}) traffic reset by admin");
|
||||||
|
return $this->success(true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error($e);
|
||||||
|
return $this->fail([500, '重置失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量重置节点流量
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function batchResetTraffic(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'ids' => 'required|array',
|
||||||
|
'ids.*' => 'integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ids = $request->input('ids');
|
||||||
|
if (empty($ids)) {
|
||||||
|
return $this->fail([400, '请选择要重置的节点']);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Server::whereIn('id', $ids)->update([
|
||||||
|
'u' => 0,
|
||||||
|
'd' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Log::info("Servers " . implode(',', $ids) . " traffic reset by admin");
|
||||||
|
return $this->success(true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error($e);
|
||||||
|
return $this->fail([500, '批量重置失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 复制节点
|
* 复制节点
|
||||||
@@ -120,11 +208,11 @@ class ManageController extends Controller
|
|||||||
public function copy(Request $request)
|
public function copy(Request $request)
|
||||||
{
|
{
|
||||||
$server = Server::find($request->input('id'));
|
$server = Server::find($request->input('id'));
|
||||||
$server->show = 0;
|
|
||||||
$server->code = null;
|
|
||||||
if (!$server) {
|
if (!$server) {
|
||||||
return $this->fail([400202, '服务器不存在']);
|
return $this->fail([400202, '服务器不存在']);
|
||||||
}
|
}
|
||||||
|
$server->show = 0;
|
||||||
|
$server->code = null;
|
||||||
Server::create($server->toArray());
|
Server::create($server->toArray());
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ class ServerSave extends FormRequest
|
|||||||
'rate_time_ranges.*.end' => 'required_with:rate_time_ranges|string|date_format:H:i',
|
'rate_time_ranges.*.end' => 'required_with:rate_time_ranges|string|date_format:H:i',
|
||||||
'rate_time_ranges.*.rate' => 'required_with:rate_time_ranges|numeric|min:0',
|
'rate_time_ranges.*.rate' => 'required_with:rate_time_ranges|numeric|min:0',
|
||||||
'protocol_settings' => 'array',
|
'protocol_settings' => 'array',
|
||||||
|
'transfer_enable' => 'nullable|integer|min:0',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +201,8 @@ class ServerSave extends FormRequest
|
|||||||
'protocol_settings.*.string' => ':attribute 必须是字符串',
|
'protocol_settings.*.string' => ':attribute 必须是字符串',
|
||||||
'protocol_settings.*.integer' => ':attribute 必须是整数',
|
'protocol_settings.*.integer' => ':attribute 必须是整数',
|
||||||
'protocol_settings.*.in' => ':attribute 的值不合法',
|
'protocol_settings.*.in' => ':attribute 的值不合法',
|
||||||
|
'transfer_enable.integer' => '流量上限必须是整数',
|
||||||
|
'transfer_enable.min' => '流量上限不能小于0',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ class AdminRoute
|
|||||||
$router->post('/drop', [ManageController::class, 'drop']);
|
$router->post('/drop', [ManageController::class, 'drop']);
|
||||||
$router->post('/copy', [ManageController::class, 'copy']);
|
$router->post('/copy', [ManageController::class, 'copy']);
|
||||||
$router->post('/sort', [ManageController::class, 'sort']);
|
$router->post('/sort', [ManageController::class, 'sort']);
|
||||||
|
$router->post('/batchDelete', [ManageController::class, 'batchDelete']);
|
||||||
|
$router->post('/resetTraffic', [ManageController::class, 'resetTraffic']);
|
||||||
|
$router->post('/batchResetTraffic', [ManageController::class, 'batchResetTraffic']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Order
|
// Order
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
use App\Models\StatServer;
|
use App\Models\StatServer;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
@@ -59,12 +61,23 @@ class StatServerJob implements ShouldQueue
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$this->processServerStat($u, $d, $recordAt);
|
$this->processServerStat($u, $d, $recordAt);
|
||||||
|
$this->updateServerTraffic($u, $d);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('StatServerJob failed for server ' . $this->server['id'] . ': ' . $e->getMessage());
|
Log::error('StatServerJob failed for server ' . $this->server['id'] . ': ' . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function updateServerTraffic(int $u, int $d): void
|
||||||
|
{
|
||||||
|
DB::table('v2_server')
|
||||||
|
->where('id', $this->server['id'])
|
||||||
|
->incrementEach(
|
||||||
|
['u' => $u, 'd' => $d],
|
||||||
|
['updated_at' => Carbon::now()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected function processServerStat(int $u, int $d, int $recordAt): void
|
protected function processServerStat(int $u, int $d, int $recordAt): void
|
||||||
{
|
{
|
||||||
$driver = config('database.default');
|
$driver = config('database.default');
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
|||||||
* @property int|null $d 下行流量
|
* @property int|null $d 下行流量
|
||||||
* @property int|null $total 总流量
|
* @property int|null $total 总流量
|
||||||
* @property-read array|null $load_status 负载状态(包含CPU、内存、交换区、磁盘信息)
|
* @property-read array|null $load_status 负载状态(包含CPU、内存、交换区、磁盘信息)
|
||||||
|
*
|
||||||
|
* @property int $transfer_enable 流量上限,0或者null表示不限制
|
||||||
|
* @property int $u 当前上传流量
|
||||||
|
* @property int $d 当前下载流量
|
||||||
*/
|
*/
|
||||||
class Server extends Model
|
class Server extends Model
|
||||||
{
|
{
|
||||||
@@ -124,6 +128,9 @@ class Server extends Model
|
|||||||
'updated_at' => 'timestamp',
|
'updated_at' => 'timestamp',
|
||||||
'rate_time_ranges' => 'array',
|
'rate_time_ranges' => 'array',
|
||||||
'rate_time_enable' => 'boolean',
|
'rate_time_enable' => 'boolean',
|
||||||
|
'transfer_enable' => 'integer',
|
||||||
|
'u' => 'integer',
|
||||||
|
'd' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
private const MULTIPLEX_CONFIGURATION = [
|
private const MULTIPLEX_CONFIGURATION = [
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ class ServerService
|
|||||||
{
|
{
|
||||||
$servers = Server::whereJsonContains('group_ids', (string) $user->group_id)
|
$servers = Server::whereJsonContains('group_ids', (string) $user->group_id)
|
||||||
->where('show', true)
|
->where('show', true)
|
||||||
|
->where(function ($query) {
|
||||||
|
$query->whereNull('transfer_enable')
|
||||||
|
->orWhere('transfer_enable', 0)
|
||||||
|
->orWhereRaw('u + d < transfer_enable');
|
||||||
|
})
|
||||||
->orderBy('sort', 'ASC')
|
->orderBy('sort', 'ASC')
|
||||||
->get()
|
->get()
|
||||||
->append(['last_check_at', 'last_push_at', 'online', 'is_online', 'available_status', 'cache_key', 'server_key']);
|
->append(['last_check_at', 'last_push_at', 'online', 'is_online', 'available_status', 'cache_key', 'server_key']);
|
||||||
@@ -244,10 +249,10 @@ class ServerService
|
|||||||
default => [],
|
default => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
$response = array_filter(
|
// $response = array_filter(
|
||||||
$response,
|
// $response,
|
||||||
static fn ($value) => $value !== null
|
// static fn ($value) => $value !== null
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (!empty($node['route_ids'])) {
|
if (!empty($node['route_ids'])) {
|
||||||
$response['routes'] = self::getRoutes($node['route_ids']);
|
$response['routes'] = self::getRoutes($node['route_ids']);
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?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::table('v2_server', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('v2_server', 'transfer_enable')) {
|
||||||
|
$table->bigInteger('transfer_enable')
|
||||||
|
->default(0)
|
||||||
|
->after('rate')
|
||||||
|
->comment('Traffic limit , 0 or null=no limit');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('v2_server', 'u')) {
|
||||||
|
$table->bigInteger('u')
|
||||||
|
->default(0)
|
||||||
|
->after('transfer_enable')
|
||||||
|
->comment('upload traffic');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('v2_server', 'd')) {
|
||||||
|
$table->bigInteger('d')
|
||||||
|
->default(0)
|
||||||
|
->after('u')
|
||||||
|
->comment('donwload traffic');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('v2_server', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['transfer_enable', 'u', 'd']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Submodule public/assets/admin updated: 89c0577191...9b2d136d81
Reference in New Issue
Block a user