orderBy('id') ->get() ->map(function (ServerMachine $machine) { return [ 'id' => $machine->id, 'name' => $machine->name, 'notes' => $machine->notes, 'is_active' => $machine->is_active, 'last_seen_at' => $machine->last_seen_at, 'load_status' => $machine->load_status, 'servers_count' => $machine->servers_count, 'created_at' => $machine->created_at, 'updated_at' => $machine->updated_at, ]; }); return $this->success($machines); } /** * 创建 / 更新机器 */ public function save(Request $request) { $params = $request->validate([ 'id' => 'nullable|integer|exists:v2_server_machine,id', 'name' => 'required|string|max:255', 'notes' => 'nullable|string', 'is_active' => 'nullable|boolean', ]); if (!empty($params['id'])) { $machine = ServerMachine::find($params['id']); $update = ['name' => $params['name']]; if (array_key_exists('notes', $params)) { $update['notes'] = $params['notes']; } if (array_key_exists('is_active', $params)) { $update['is_active'] = $params['is_active']; } $machine->update($update); return $this->success(true); } $machine = ServerMachine::create([ 'name' => $params['name'], 'notes' => $params['notes'] ?? null, 'is_active' => $params['is_active'] ?? true, 'token' => ServerMachine::generateToken(), ]); return $this->success([ 'id' => $machine->id, 'token' => $machine->token, ]); } /** * 重置机器 Token */ public function resetToken(Request $request) { $params = $request->validate([ 'id' => 'required|integer|exists:v2_server_machine,id', ]); $machine = ServerMachine::find($params['id']); $token = ServerMachine::generateToken(); $machine->update(['token' => $token]); return $this->success(['token' => $token]); } /** * 获取机器 Token(仅展示一次,用于首次配置) */ public function getToken(Request $request) { $params = $request->validate([ 'id' => 'required|integer|exists:v2_server_machine,id', ]); $machine = ServerMachine::find($params['id']); return $this->success(['token' => $machine->token]); } /** * 获取机器模式一键安装命令 */ public function installCommand(Request $request) { $params = $request->validate([ 'id' => 'required|integer|exists:v2_server_machine,id', ]); $machine = ServerMachine::find($params['id']); return $this->success([ 'command' => $this->buildInstallCommand($request, $machine), ]); } /** * 删除机器(自动解除关联节点) */ public function drop(Request $request) { $params = $request->validate([ 'id' => 'required|integer|exists:v2_server_machine,id', ]); $machine = ServerMachine::find($params['id']); $machineId = $machine->id; // Detach nodes first (sets machine_id = null), then delete and notify Server::where('machine_id', $machineId)->update(['machine_id' => null]); $machine->delete(); // Notify with empty node list so WS process cleans up registry NodeSyncService::notifyMachineNodesChanged($machineId); return $this->success(true); } /** * 获取机器下的节点列表 */ public function nodes(Request $request) { $params = $request->validate([ 'machine_id' => 'required|integer|exists:v2_server_machine,id', ]); $nodes = Server::where('machine_id', $params['machine_id']) ->orderBy('sort') ->get(['id', 'name', 'type', 'host', 'port', 'show', 'enabled', 'sort']); return $this->success($nodes); } /** * 获取机器负载历史 */ public function history(Request $request) { $params = $request->validate([ 'machine_id' => 'required|integer|exists:v2_server_machine,id', 'limit' => 'nullable|integer|min:10|max:1440', ]); $limit = (int) ($params['limit'] ?? 60); $history = ServerMachineLoadHistory::query() ->where('machine_id', $params['machine_id']) ->orderByDesc('recorded_at') ->limit($limit) ->get([ 'cpu', 'mem_total', 'mem_used', 'disk_total', 'disk_used', 'recorded_at', ]) ->reverse() ->values(); return $this->success($history); } private function buildInstallCommand(Request $request, ServerMachine $machine): string { $panelUrl = rtrim((string) (config('app.url') ?: $request->getSchemeAndHttpHost()), '/'); $installerUrl = 'https://raw.githubusercontent.com/cedar2025/xboard-node/main/install.sh'; return sprintf( 'bash <(curl -fsSL %s) --panel %s --token %s --machine-id %d --yes', escapeshellarg($installerUrl), escapeshellarg($panelUrl), escapeshellarg($machine->token), $machine->id ); } }