312 lines
12 KiB
PHP
312 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\AccessLog;
|
|
use App\Models\OpsProtocol;
|
|
use App\Models\OpsSoftware;
|
|
use App\Models\User;
|
|
use App\Models\UserOpsSoftwarePreference;
|
|
use hg\apidoc\annotation as Apidoc;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
#[Apidoc\Title('运维协议与软件管理')]
|
|
class OpsClientController extends Controller
|
|
{
|
|
public function __construct()
|
|
{
|
|
$this->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,
|
|
],
|
|
]);
|
|
}
|
|
}
|