feat: add AnyTLS support and improve system functionality

- Add AnyTLS protocol support
- Add system logs viewing in admin panel
- Refactor client subscription delivery code
- Refactor hook mechanism
- Add plugin support for Shadowsocks protocol
- Add CSV export option for batch user creation
- Fix mobile admin login page width display issue
This commit is contained in:
xboard
2025-05-22 17:58:22 +08:00
parent 2580475f78
commit fc5a957ddd
35 changed files with 1586 additions and 640 deletions

View File

@@ -21,24 +21,51 @@ class SystemController extends Controller
$data = [
'schedule' => $this->getScheduleStatus(),
'horizon' => $this->getHorizonStatus(),
'schedule_last_runtime' => Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null))
'schedule_last_runtime' => Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null)),
'logs' => $this->getLogStatistics()
];
return $this->success($data);
}
/**
* 获取日志统计信息
*
* @return array 各级别日志的数量统计
*/
protected function getLogStatistics(): array
{
// 初始化日志统计数组
$statistics = [
'info' => 0,
'warning' => 0,
'error' => 0,
'total' => 0
];
if (class_exists(LogModel::class) && LogModel::count() > 0) {
$statistics['info'] = LogModel::where('level', 'info')->count();
$statistics['warning'] = LogModel::where('level', 'warning')->count();
$statistics['error'] = LogModel::where('level', 'error')->count();
$statistics['total'] = LogModel::count();
return $statistics;
}
return $statistics;
}
public function getQueueWorkload(WorkloadRepository $workload)
{
return $this->success(collect($workload->get())->sortBy('name')->values()->toArray());
}
protected function getScheduleStatus():bool
protected function getScheduleStatus(): bool
{
return (time() - 120) < Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null));
}
protected function getHorizonStatus():bool
protected function getHorizonStatus(): bool
{
if (! $masters = app(MasterSupervisorRepository::class)->all()) {
if (!$masters = app(MasterSupervisorRepository::class)->all()) {
return false;
}
@@ -88,7 +115,7 @@ class SystemController extends Controller
*/
protected function totalPausedMasters()
{
if (! $masters = app(MasterSupervisorRepository::class)->all()) {
if (!$masters = app(MasterSupervisorRepository::class)->all()) {
return 0;
}
@@ -97,7 +124,8 @@ class SystemController extends Controller
})->count();
}
public function getSystemLog(Request $request) {
public function getSystemLog(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
$builder = LogModel::orderBy('created_at', 'DESC')
@@ -110,4 +138,25 @@ class SystemController extends Controller
'total' => $total
]);
}
public function getHorizonFailedJobs(Request $request, JobRepository $jobRepository)
{
$current = max(1, (int) $request->input('current', 1));
$pageSize = max(10, (int) $request->input('page_size', 20));
$offset = ($current - 1) * $pageSize;
$failedJobs = collect($jobRepository->getFailed())
->sortByDesc('failed_at')
->slice($offset, $pageSize)
->values();
$total = $jobRepository->countFailed();
return response()->json([
'data' => $failedJobs,
'total' => $total,
'current' => $current,
'page_size' => $pageSize,
]);
}
}

View File

@@ -367,7 +367,7 @@ class UserController extends Controller
return $this->success(true);
}
if ($request->input('generate_count')) {
$this->multiGenerate($request);
return $this->multiGenerate($request);
}
}
@@ -406,15 +406,44 @@ class UserController extends Controller
Log::error($e);
return $this->fail([500, '生成失败']);
}
$data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n";
foreach ($users as $user) {
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$createDate = date('Y-m-d H:i:s', $user['created_at']);
$password = $request->input('password') ?? $user['email'];
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
$data .= "{$user['email']},{$password},{$expireDate},{$user['uuid']},{$createDate},{$subscribeUrl}\r\n";
// 判断是否导出 CSV
if ($request->input('download_csv')) {
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="users.csv"',
];
$callback = function () use ($users, $request) {
$handle = fopen('php://output', 'w');
fputcsv($handle, ['账号', '密码', '过期时间', 'UUID', '创建时间', '订阅地址']);
foreach ($users as $user) {
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$createDate = date('Y-m-d H:i:s', $user['created_at']);
$password = $request->input('password') ?? $user['email'];
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
fputcsv($handle, [$user['email'], $password, $expireDate, $user['uuid'], $createDate, $subscribeUrl]);
}
fclose($handle);
};
return response()->streamDownload($callback, 'users.csv', $headers);
}
echo $data;
// 默认返回 JSON
$data = collect($users)->map(function ($user) use ($request) {
return [
'email' => $user['email'],
'password' => $request->input('password') ?? $user['email'],
'expired_at' => $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']),
'uuid' => $user['uuid'],
'created_at' => date('Y-m-d H:i:s', $user['created_at']),
'subscribe_url' => Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']),
];
});
return response()->json([
'code' => 0,
'message' => '批量生成成功',
'data' => $data,
]);
}
public function sendMail(UserSendMail $request)