middleware('auth:api'); $this->middleware('permission:platform.servers.manage,api')->only([ 'storeProtocol', 'updateProtocol', 'destroyProtocol', 'storeSoftware', 'updateSoftware', 'destroySoftware', ]); } #[Apidoc\Title('运维协议与软件列表'), Apidoc\Method('GET'), Apidoc\Url('/ops-clients/meta')] public function meta(Request $request): JsonResponse { /** @var User|null $user */ $user = auth('api')->user(); $isManager = (bool) ($user?->can('platform.servers.manage')); $protocolQuery = OpsProtocol::query()->orderBy('sort')->orderBy('id'); if (! $isManager) { $protocolQuery->where('is_active', true); } $protocols = $protocolQuery->with(['softwares' => function ($query) use ($isManager) { if (! $isManager) { $query->where('is_active', true); } }])->get(); $preferences = UserOpsSoftwarePreference::query() ->where('user_id', $user?->id) ->get() ->mapWithKeys(fn (UserOpsSoftwarePreference $item): array => [ (string) $item->ops_protocol_id => $item->ops_software_id, ]); return response()->json([ 'code' => 0, 'message' => 'ok', 'data' => [ 'protocols' => $protocols, 'preferences' => $preferences, 'is_manager' => $isManager, ], ]); } #[Apidoc\Title('新增运维协议'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/protocols')] public function storeProtocol(Request $request): JsonResponse { $validated = $request->validate([ 'name' => ['required', 'string', 'max:64', 'unique:ops_protocols,name'], 'bastion_protocol_id' => ['required', 'integer', 'min:1'], 'description' => ['nullable', 'string', 'max:255'], 'sort' => ['sometimes', 'integer', 'min:0'], 'is_active' => ['sometimes', 'boolean'], ]); $protocol = OpsProtocol::query()->create($validated); $this->auditLog($request, 'ops_protocol_create', ['metadata' => ['ops_protocol_id' => $protocol->id]]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => $protocol], 201); } #[Apidoc\Title('修改运维协议'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/protocols/{id}')] public function updateProtocol(Request $request, int $id): JsonResponse { $protocol = OpsProtocol::query()->findOrFail($id); $validated = $request->validate([ 'name' => ['required', 'string', 'max:64', 'unique:ops_protocols,name,'.$protocol->id], 'bastion_protocol_id' => ['required', 'integer', 'min:1'], 'description' => ['nullable', 'string', 'max:255'], 'sort' => ['sometimes', 'integer', 'min:0'], 'is_active' => ['sometimes', 'boolean'], ]); $protocol->update($validated); $this->auditLog($request, 'ops_protocol_update', ['metadata' => ['ops_protocol_id' => $protocol->id]]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => $protocol]); } #[Apidoc\Title('删除运维协议'), Apidoc\Method('DELETE'), Apidoc\Url('/ops-clients/protocols/{id}')] public function destroyProtocol(Request $request, int $id): JsonResponse { $protocol = OpsProtocol::query()->findOrFail($id); $this->auditLog($request, 'ops_protocol_delete', ['metadata' => ['ops_protocol_id' => $protocol->id]]); $protocol->delete(); return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]); } #[Apidoc\Title('新增运维软件'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/protocols/{id}/softwares')] public function storeSoftware(Request $request, int $id): JsonResponse { OpsProtocol::query()->findOrFail($id); $validated = $request->validate([ 'name' => ['required', 'string', 'max:100'], 'client_path' => ['nullable', 'string', 'max:255'], 'sort' => ['sometimes', 'integer', 'min:0'], 'is_active' => ['sometimes', 'boolean'], ]); $exists = OpsSoftware::query() ->where('ops_protocol_id', $id) ->where('name', $validated['name']) ->exists(); if ($exists) { throw ValidationException::withMessages([ 'name' => ['同一协议下软件名称不能重复。'], ]); } $software = OpsSoftware::query()->create([ ...$validated, 'ops_protocol_id' => $id, ]); $this->auditLog($request, 'ops_software_create', ['metadata' => ['ops_software_id' => $software->id, 'ops_protocol_id' => $id]]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => $software], 201); } #[Apidoc\Title('修改运维软件'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/softwares/{id}')] public function updateSoftware(Request $request, int $id): JsonResponse { $software = OpsSoftware::query()->findOrFail($id); $validated = $request->validate([ 'name' => ['required', 'string', 'max:100'], 'client_path' => ['nullable', 'string', 'max:255'], 'sort' => ['sometimes', 'integer', 'min:0'], 'is_active' => ['sometimes', 'boolean'], ]); $exists = OpsSoftware::query() ->where('ops_protocol_id', $software->ops_protocol_id) ->where('name', $validated['name']) ->where('id', '!=', $software->id) ->exists(); if ($exists) { throw ValidationException::withMessages([ 'name' => ['同一协议下软件名称不能重复。'], ]); } $software->update($validated); $this->auditLog($request, 'ops_software_update', ['metadata' => ['ops_software_id' => $software->id]]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => $software]); } #[Apidoc\Title('删除运维软件'), Apidoc\Method('DELETE'), Apidoc\Url('/ops-clients/softwares/{id}')] public function destroySoftware(Request $request, int $id): JsonResponse { $software = OpsSoftware::query()->findOrFail($id); $this->auditLog($request, 'ops_software_delete', ['metadata' => ['ops_software_id' => $software->id]]); $software->delete(); return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]); } #[Apidoc\Title('保存我的运维软件偏好'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/preferences')] public function savePreferences(Request $request): JsonResponse { $validated = $request->validate([ 'items' => ['required', 'array'], 'items.*.protocol_id' => ['required', 'integer', 'exists:ops_protocols,id'], 'items.*.software_id' => ['nullable', 'integer', 'exists:ops_softwares,id'], ]); /** @var User $user */ $user = auth('api')->user(); foreach ($validated['items'] as $item) { $protocolId = (int) $item['protocol_id']; $softwareId = isset($item['software_id']) ? (int) $item['software_id'] : null; if ($softwareId !== null) { $software = OpsSoftware::query()->findOrFail($softwareId); if ((int) $software->ops_protocol_id !== $protocolId) { throw ValidationException::withMessages([ 'items' => ['软件与协议不匹配。'], ]); } UserOpsSoftwarePreference::query()->updateOrCreate( ['user_id' => $user->id, 'ops_protocol_id' => $protocolId], ['ops_software_id' => $softwareId] ); } else { UserOpsSoftwarePreference::query() ->where('user_id', $user->id) ->where('ops_protocol_id', $protocolId) ->delete(); } } $this->auditLog($request, 'ops_preference_update', ['metadata' => ['user_id' => $user->id]]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]); } #[Apidoc\Title('生成运维连接'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/link')] public function generateLink(Request $request): JsonResponse { $validated = $request->validate([ 'protocol_id' => ['required', 'integer', 'exists:ops_protocols,id'], 'software_id' => ['nullable', 'integer', 'exists:ops_softwares,id'], ]); /** @var User $user */ $user = auth('api')->user(); $protocol = OpsProtocol::query()->findOrFail((int) $validated['protocol_id']); $softwareId = isset($validated['software_id']) ? (int) $validated['software_id'] : null; if ($softwareId === null) { $softwareId = (int) UserOpsSoftwarePreference::query() ->where('user_id', $user->id) ->where('ops_protocol_id', $protocol->id) ->value('ops_software_id'); } $software = OpsSoftware::query()->find($softwareId ?: 0); if (! $software || (int) $software->ops_protocol_id !== (int) $protocol->id) { throw ValidationException::withMessages([ 'software_id' => ['请先选择该协议对应的软件。'], ]); } $payload = [ 'NODE_COMMON' => [ 'Mode' => '1', 'IsGlobalSetting' => '0', 'IPv4' => (string) config('services.ops_client.ipv4', '172.16.1.2'), 'AssetIPv4' => (string) config('services.ops_client.asset_ipv4', '0.0.0.0'), 'Port' => '', 'AssetPort' => '', 'Protocol' => (string) $protocol->name, 'ClientName' => (string) $software->name, 'ClientPath' => (string) ($software->client_path ?? ''), 'Username' => '', 'SSOToken' => '', 'Title' => '', ], 'NODE_SFTP' => [ 'NotUsed' => '1', 'Char-Set' => 'Utf=1', ], ]; $encoded = base64_encode((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); $link = 'dasusm://'.$encoded; UserOpsSoftwarePreference::query()->updateOrCreate( ['user_id' => $user->id, 'ops_protocol_id' => $protocol->id], ['ops_software_id' => $software->id] ); AccessLog::query()->create([ 'user_id' => $user->id, 'server_resource_id' => null, 'bastion_account_id' => null, 'protocol' => (string) $protocol->name, 'action' => 'ops_client_link', 'requested_at' => now(), 'metadata' => [ 'ops_protocol_id' => $protocol->id, 'ops_software_id' => $software->id, 'ops_software_name' => $software->name, ], ]); $this->auditLog($request, 'ops_client_link_generate', [ 'metadata' => [ 'ops_protocol_id' => $protocol->id, 'ops_software_id' => $software->id, ], ]); return response()->json([ 'code' => 0, 'message' => 'ok', 'data' => [ 'link' => $link, 'encoded' => $encoded, 'payload' => $payload, 'protocol' => $protocol, 'software' => $software, ], ]); } }