diff --git a/app/Channels/DingTalkChannel.php b/app/Channels/DingTalkChannel.php index 6fcdfb61..53603876 100644 --- a/app/Channels/DingTalkChannel.php +++ b/app/Channels/DingTalkChannel.php @@ -49,11 +49,11 @@ class DingTalkChannel 'btnOrientation' => 1, 'btns' => [ [ - 'title' => '否 決', + 'title' => trans('common.status.reject'), 'actionURL' => $message['button'][0], ], [ - 'title' => '确 认', + 'title' => trans('common.confirm'), 'actionURL' => $message['button'][1], ], ], diff --git a/app/Channels/WeChatChannel.php b/app/Channels/WeChatChannel.php index bede037b..717a5d7e 100644 --- a/app/Channels/WeChatChannel.php +++ b/app/Channels/WeChatChannel.php @@ -33,13 +33,13 @@ class WeChatChannel 'button_list' => [ [ 'type' => 1, - 'text' => '否 決', + 'text' => trans('common.status.reject'), 'style' => 3, 'url' => $message['button'][0], ], [ 'type' => 1, - 'text' => '确 认', + 'text' => trans('common.confirm'), 'style' => 1, 'url' => $message['button'][1], ], diff --git a/app/Helpers/ClientConfig.php b/app/Helpers/ClientConfig.php index 387b2a05..b09a7aa2 100644 --- a/app/Helpers/ClientConfig.php +++ b/app/Helpers/ClientConfig.php @@ -4,10 +4,8 @@ namespace App\Helpers; use App\Components\Client\Clash; use App\Components\Client\QuantumultX; -use App\Components\Client\Surfboard; use App\Components\Client\Surge; use App\Components\Client\URLSchemes; -use App\Components\Client\V2rayN; use File; use Symfony\Component\Yaml\Yaml; @@ -105,7 +103,7 @@ trait ClientConfig { $user = $this->getUser(); $webName = sysConfig('website_name'); - header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($webName)); + header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($webName).'.yaml'); header('profile-update-interval: 24'); header('profile-web-page-url:'.sysConfig('website_url')); if (sysConfig('is_custom_subscribe')) { diff --git a/app/Http/Controllers/Admin/NodeController.php b/app/Http/Controllers/Admin/NodeController.php index 6b19cdda..f485d149 100644 --- a/app/Http/Controllers/Admin/NodeController.php +++ b/app/Http/Controllers/Admin/NodeController.php @@ -47,9 +47,8 @@ class NodeController extends Controller return view('admin.node.index', ['nodeList' => $nodeList]); } - // 添加节点 public function store(NodeRequest $request): JsonResponse - { + { // 添加节点 try { if ($node = Node::create($this->nodeStore($request->validated()))) { // 生成节点标签 @@ -80,14 +79,149 @@ class NodeController extends Controller ]); } - // 添加节点信息 + public function clone(Node $node) + { // 克隆节点 + $new = $node->replicate()->fill([ + 'name' => $node->name.'_克隆', + 'server' => null, + ]); + $new->save(); + + return redirect()->route('admin.node.edit', $new); + } + + public function edit(Node $node) + { // 编辑节点页面 + return view('admin.node.info', [ + 'node' => $node, + 'nodes' => Node::whereNotIn('id', [$node->id])->orderBy('id')->pluck('id', 'name'), + 'countries' => Country::orderBy('code')->get(), + 'levels' => Level::orderBy('level')->get(), + 'ruleGroups' => RuleGroup::orderBy('id')->get(), + 'labels' => Label::orderByDesc('sort')->orderBy('id')->get(), + 'certs' => NodeCertificate::orderBy('id')->get(), + ]); + } + + public function update(NodeRequest $request, Node $node): JsonResponse + { // 编辑节点 + try { + if ($node->update($this->nodeStore($request->validated()))) { + // 更新节点标签 + $node->labels()->sync($request->input('labels')); + + return Response::json(['status' => 'success', 'message' => '编辑成功']); + } + } catch (Exception $e) { + Log::error('编辑节点信息异常:'.$e->getMessage()); + + return Response::json(['status' => 'fail', 'message' => '编辑失败:'.$e->getMessage()]); + } + + return Response::json(['status' => 'fail', 'message' => '编辑失败']); + } + + public function destroy(Node $node): JsonResponse + { // 删除节点 + try { + if ($node->delete()) { + return Response::json(['status' => 'success', 'message' => '删除成功']); + } + } catch (Exception $e) { + Log::error('删除线路失败:'.$e->getMessage()); + + return Response::json(['status' => 'fail', 'message' => '删除线路失败:'.$e->getMessage()]); + } + + return Response::json(['status' => 'fail', 'message' => '删除线路失败']); + } + + public function checkNode(Node $node): JsonResponse + { // 节点IP阻断检测 + foreach ($node->ips() as $ip) { + $icmp = (new NetworkDetection)->networkCheck($ip, true, $node->port ?? 22); // ICMP + $tcp = (new NetworkDetection)->networkCheck($ip, false, $node->port ?? 22); // TCP + $data[$ip] = [$icmp ? config('common.network_status')[$icmp] : ' ', $tcp ? config('common.network_status')[$tcp] : ' ']; + } + + return Response::json(['status' => 'success', 'title' => '['.$node->name.']阻断信息', 'message' => $data ?? []]); + } + + public function refreshGeo($id): JsonResponse + { // 刷新节点地理位置 + $ret = false; + if ($id) { + $ret = Node::findOrFail($id)->refresh_geo(); + } else { + foreach (Node::whereStatus(1)->get() as $node) { + $result = $node->refresh_geo(); + if ($result && ! $ret) { + $ret = true; + } + } + } + + if ($ret) { + return Response::json(['status' => 'success', 'message' => '获取地理位置更新成功!']); + } + + return Response::json(['status' => 'fail', 'message' => '【存在】获取地理位置更新失败!']); + } + + public function reload($id): JsonResponse + { // 重载节点 + $ret = false; + if ($id) { + $node = Node::findOrFail($id); + $ret = reloadNode::dispatchNow($node); + } else { + foreach (Node::whereStatus(1)->whereType(4)->get() as $node) { + $result = reloadNode::dispatchNow($node); + if ($result && ! $ret) { + $ret = true; + } + } + } + + if ($ret) { + return Response::json(['status' => 'success', 'message' => '重载成功!']); + } + + return Response::json(['status' => 'fail', 'message' => '【存在】重载失败!']); + } + + public function nodeMonitor(Node $node) + { // 节点流量监控 + return view('admin.node.monitor', array_merge(['nodeName' => $node->name, 'nodeServer' => $node->server], $this->DataFlowChart($node->id, true))); + } + + public function pingNode(Node $node): JsonResponse + { // Ping节点延迟 + if ($node->is_ddns) { + if ($result = (new NetworkDetection)->ping($node->server)) { + return Response::json(['status' => 'success', 'message' => $result]); + } + } else { + $msg = null; + foreach ($node->ips() as $ip) { + $ret = (new NetworkDetection)->ping($ip); + if ($ret !== false) { + $msg .= $ret.'
'; + } + } + if (isset($msg)) { + return Response::json(['status' => 'success', 'message' => $msg]); + } + } + + return Response::json(['status' => 'fail', 'message' => 'Ping访问失败']); + } + private function nodeStore(array $info): array - { + { // 添加节点信息 switch ($info['type']) { case 0: - $profile = [ - 'method' => $info['method'], - ]; + $profile = ['method' => $info['method']]; break; case 2: $profile = [ @@ -137,158 +271,11 @@ class NodeController extends Controller 'is_display' => $info['is_display'], 'is_ddns' => $info['is_ddns'], 'relay_node_id' => $info['relay_node_id'], - 'port' => $info['port'], + 'port' => $info['port'] ?? 0, 'push_port' => $info['push_port'], 'detection_type' => $info['detection_type'], 'sort' => $info['sort'], 'status' => $info['status'], ]; } - - // 克隆节点 - public function clone(Node $node) - { - $new = $node->replicate()->fill([ - 'name' => $node->name.'_克隆', - 'server' => null, - ]); - $new->save(); - - return redirect()->route('admin.node.edit', $new); - } - - // 编辑节点页面 - public function edit(Node $node) - { - return view('admin.node.info', [ - 'node' => $node, - 'nodes' => Node::whereNotIn('id', [$node->id])->orderBy('id')->pluck('id', 'name'), - 'countries' => Country::orderBy('code')->get(), - 'levels' => Level::orderBy('level')->get(), - 'ruleGroups' => RuleGroup::orderBy('id')->get(), - 'labels' => Label::orderByDesc('sort')->orderBy('id')->get(), - 'certs' => NodeCertificate::orderBy('id')->get(), - ]); - } - - // 编辑节点 - public function update(NodeRequest $request, Node $node): JsonResponse - { - try { - if ($node->update($this->nodeStore($request->validated()))) { - // 更新节点标签 - $node->labels()->sync($request->input('labels')); - - return Response::json(['status' => 'success', 'message' => '编辑成功']); - } - } catch (Exception $e) { - Log::error('编辑节点信息异常:'.$e->getMessage()); - - return Response::json(['status' => 'fail', 'message' => '编辑失败:'.$e->getMessage()]); - } - - return Response::json(['status' => 'fail', 'message' => '编辑失败']); - } - - // 删除节点 - public function destroy(Node $node): JsonResponse - { - try { - if ($node->delete()) { - return Response::json(['status' => 'success', 'message' => '删除成功']); - } - } catch (Exception $e) { - Log::error('删除线路失败:'.$e->getMessage()); - - return Response::json(['status' => 'fail', 'message' => '删除线路失败:'.$e->getMessage()]); - } - - return Response::json(['status' => 'fail', 'message' => '删除线路失败']); - } - - // 节点IP阻断检测 - public function checkNode(Node $node): JsonResponse - { - foreach ($node->ips() as $ip) { - $icmp = (new NetworkDetection)->networkCheck($ip, true, $node->port ?? 22); // ICMP - $tcp = (new NetworkDetection)->networkCheck($ip, false, $node->port ?? 22); // TCP - $data[$ip] = [$icmp ? config('common.network_status')[$icmp] : ' ', $tcp ? config('common.network_status')[$tcp] : ' ']; - } - - return Response::json(['status' => 'success', 'title' => '['.$node->name.']阻断信息', 'message' => $data ?? []]); - } - - // 刷新节点地理位置 - public function refreshGeo($id): JsonResponse - { - $ret = false; - if ($id) { - $ret = Node::findOrFail($id)->refresh_geo(); - } else { - foreach (Node::whereStatus(1)->get() as $node) { - $result = $node->refresh_geo(); - if ($result && ! $ret) { - $ret = true; - } - } - } - - if ($ret) { - return Response::json(['status' => 'success', 'message' => '获取地理位置更新成功!']); - } - - return Response::json(['status' => 'fail', 'message' => '【存在】获取地理位置更新失败!']); - } - - // 重载节点 - public function reload($id): JsonResponse - { - $ret = false; - if ($id) { - $node = Node::findOrFail($id); - $ret = reloadNode::dispatchNow($node); - } else { - foreach (Node::whereStatus(1)->whereType(4)->get() as $node) { - $result = reloadNode::dispatchNow($node); - if ($result && ! $ret) { - $ret = true; - } - } - } - - if ($ret) { - return Response::json(['status' => 'success', 'message' => '重载成功!']); - } - - return Response::json(['status' => 'fail', 'message' => '【存在】重载失败!']); - } - - // 节点流量监控 - public function nodeMonitor(Node $node) - { - return view('admin.node.monitor', array_merge(['nodeName' => $node->name, 'nodeServer' => $node->server], $this->DataFlowChart($node->id, true))); - } - - // Ping节点延迟 - public function pingNode(Node $node): JsonResponse - { - if ($node->is_ddns) { - if ($result = (new NetworkDetection)->ping($node->server)) { - return Response::json(['status' => 'success', 'message' => $result]); - } - } else { - $msg = null; - foreach ($node->ips() as $ip) { - $ret = (new NetworkDetection)->ping($ip); - if ($ret !== false) { - $msg .= $ret.'
'; - } - } - if (isset($msg)) { - return Response::json(['status' => 'success', 'message' => $msg]); - } - } - - return Response::json(['status' => 'fail', 'message' => 'Ping访问失败']); - } } diff --git a/app/Http/Requests/Admin/NodeRequest.php b/app/Http/Requests/Admin/NodeRequest.php index 3e43203b..d16f0259 100644 --- a/app/Http/Requests/Admin/NodeRequest.php +++ b/app/Http/Requests/Admin/NodeRequest.php @@ -35,7 +35,7 @@ class NodeRequest extends FormRequest 'is_display' => 'required|numeric|between:0,3', 'detection_type' => 'required|numeric|between:0,3', 'single' => 'required|boolean', - 'port' => 'required_unless:single,0|numeric|between:1,65535|different:push_port', + 'port' => 'required_unless:single,"0"|numeric|between:1,65535|different:push_port', 'passwd' => 'exclude_unless:type,1,type,4|required_if:single,1|string|nullable', 'v2_alter_id' => 'nullable|numeric|between:0,65535', 'v2_method' => 'required_if:type,2', diff --git a/app/Models/NotificationLog.php b/app/Models/NotificationLog.php index 9cc97c12..b131677f 100644 --- a/app/Models/NotificationLog.php +++ b/app/Models/NotificationLog.php @@ -15,6 +15,6 @@ class NotificationLog extends Model // 通知类型 public function getTypeLabelAttribute(): string { - return config('common.notification.labels')[$this->attributes['type']] ?? '未知'; + return config('common.notification.labels')[$this->attributes['type']] ?? trans('common.status.unknown'); } } diff --git a/config/tasks.php b/config/tasks.php index 577fc54b..758fd5d4 100644 --- a/config/tasks.php +++ b/config/tasks.php @@ -1,30 +1,30 @@ 1000, // 大数据量修改,分段处理,减少内存使用 + 'chunk' => env('TASKS_CHUNK', 1000), // 大数据量修改,分段处理,减少内存使用 'clean' => [ - 'node_daily_logs' => '-2 month', // 清除节点每天流量数据日志 - 'node_hourly_logs' => '-3 days', // 清除节点每小时流量数据日志 - 'notification_logs' => '-1 month', // 清理通知日志 - 'node_heartbeats' => '-30 minutes', // 清除节点负载信息日志 - 'node_online_logs' => '-1 hour', // 清除节点在线用户数日志 - 'payments' => '-1 year', // 清理在线支付日志 - 'rule_logs' => '-3 month', // 清理审计触发日志 - 'node_online_ips' => '-1 week', // 清除用户连接IP - 'user_baned_logs' => '-3 month', // 清除用户封禁日志 - 'user_daily_logs_nodes' => '-1 month', // 清除用户各节点的每天流量数据日志 - 'user_daily_logs_total' => '-3 month', // 清除用户节点总计的每天流量数据日志 - 'user_hourly_logs' => '-3 days', // 清除用户每时各流量数据日志 最少值为 2 - 'login_logs' => '-3 month', // 清除用户登陆日志 - 'subscribe_logs' => '-1 month', // 清理用户订阅请求日志 - 'traffic_logs' => '-3 days', // 清除用户流量日志 + 'node_daily_logs' => env('TASKS_NODE_DAILY_LOGS', '-2 month'), // 清除节点每天流量数据日志 + 'node_hourly_logs' => env('TASKS_NODE_HOURLY_LOGS', '-3 days'), // 清除节点每小时流量数据日志 + 'notification_logs' => env('TASKS_NOTIFICATION_LOGS', '-1 month'), // 清理通知日志 + 'node_heartbeats' => env('TASKS_NODE_HEARTBEATS', '-30 minutes'), // 清除节点负载信息日志 + 'node_online_logs' => env('TASKS_NODE_ONLINE_LOGS', '-1 hour'), // 清除节点在线用户数日志 + 'payments' => env('TASKS_PAYMENTS', '-1 year'), // 清理在线支付日志 + 'rule_logs' => env('TASKS_RULE_lOGS', '-3 month'), // 清理审计触发日志 + 'node_online_ips' => env('TASKS_NODE_ONLINE_IPS', '-1 week'), // 清除用户连接IP + 'user_baned_logs' => env('TASKS_USER_BANED_LOGS', '-3 month'), // 清除用户封禁日志 + 'user_daily_logs_nodes' => env('TASKS_USER_DAILY_LOGS_NODES', '-1 month'), // 清除用户各节点的每天流量数据日志 + 'user_daily_logs_total' => env('TASKS_USER_DAILY_LOGS_TOTAL', '-3 month'), // 清除用户节点总计的每天流量数据日志 + 'user_hourly_logs' => env('TASKS_USER_HOURLY_LOGS', '-3 days'), // 清除用户每时各流量数据日志 最少值为 2 + 'login_logs' => env('TASKS_LOGIN_LOGS', '-3 month'), // 清除用户登陆日志 + 'subscribe_logs' => env('TASKS_SUBSCRIBE_LOGS', '-1 month'), // 清理用户订阅请求日志 + 'traffic_logs' => env('TASKS_TRAFFIC_LOGS', '-3 days'), // 清除用户流量日志 ], 'close' => [ - 'tickets' => 72, // 自动关闭工单,单位:小时 - 'confirmation_orders' => 12, // 自动关闭人工支付订单,单位:小时 - 'orders' => 15, // 自动关闭订单,单位:分钟 - 'verify' => 15, // 自动失效验证码,单位:分钟 + 'tickets' => env('TASKS_TICKETS', 72), // 自动关闭工单,单位:小时 + 'confirmation_orders' => env('TASKS_CONFIRMATION_ORDERS', 12), // 自动关闭人工支付订单,单位:小时 + 'orders' => env('TASKS_ORDERS', 15), // 自动关闭订单,单位:分钟 + 'verify' => env('TASKS_VERIFY', 15), // 自动失效验证码,单位:分钟 ], - 'release_port' => 30, // 端口自动释放,单位:天 - 'recently_heartbeat' => '-10 minutes', // 节点近期负载 + 'release_port' => env('TASKS_RELEASE_PORT', 30), // 端口自动释放,单位:天 + 'recently_heartbeat' => env('TASKS_RECENTLY_HEARTBEAT', '-10 minutes'), // 节点近期负载 ]; diff --git a/config/theme.php b/config/theme.php index 12587323..076230a3 100644 --- a/config/theme.php +++ b/config/theme.php @@ -2,10 +2,10 @@ // 如何使用请参考 https://proxypanel.gitbook.io/wiki/page-modify#theme return [ - 'sidebar' => 'site-menubar-light', + 'sidebar' => env('THEME_SIDEBAR', 'site-menubar-light'), 'navbar' => [ - 'inverse' => 'navbar-inverse', - 'skin' => 'bg-indigo-600', + 'inverse' => env('THEME_NAVBAR_INVERSE', 'navbar-inverse'), + 'skin' => env('THEME_NAVBAR_SKIN', 'bg-indigo-600'), ], - 'skin' => '', + 'skin' => env('THEME_SKIN'), ]; diff --git a/resources/lang/zh_CN/common.php b/resources/lang/zh_CN/common.php index a2ae0298..8e183f66 100644 --- a/resources/lang/zh_CN/common.php +++ b/resources/lang/zh_CN/common.php @@ -124,7 +124,7 @@ return [ 'pending' => '待处理', 'unknown' => '未 知', 'available' => '生效中', - 'reject' => '驳 回', + 'reject' => '否 決', 'rejected' => '已驳回', 'review' => '待审核', 'reviewed' => '审核通过待打款',