From 8fb3131399be8da7c09cc2f99887389241489e63 Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Tue, 3 Feb 2026 21:37:46 +0300 Subject: [PATCH] =?utf8?q?[ERP-50-J]=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?utf8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?utf8?q?=D1=80=D0=BA=D0=B8=20=D0=BA=D0=B0=D0=BC=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + erp24/controllers/DiagnosticController.php | 181 ++++++++++++++++++++- erp24/views/diagnostic/index.php | 140 +++++++++++++++- 3 files changed, 319 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 74ddd0c7..bf51f526 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ hive-mind-prompt-*.txt # Auto Claude data directory .auto-claude/ +.env.jira diff --git a/erp24/controllers/DiagnosticController.php b/erp24/controllers/DiagnosticController.php index 644f028f..cf018dc0 100644 --- a/erp24/controllers/DiagnosticController.php +++ b/erp24/controllers/DiagnosticController.php @@ -514,6 +514,171 @@ class DiagnosticController extends Controller } } + /** + * Тестирование подключения к камере Дом.ру + * + * @param int $id Номер камеры (1-6) + */ + public function actionTestCamera(int $id = 1): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + if ($id < 1 || $id > 6) { + return $this->asJson([ + 'success' => false, + 'message' => 'Неверный номер камеры', + 'hint' => 'Номер камеры должен быть от 1 до 6', + ]); + } + + $login = getenv("CAMERA_{$id}_LOGIN") ?: ''; + $password = getenv("CAMERA_{$id}_PASSWORD") ?: ''; + + if (empty($login) || empty($password)) { + return $this->asJson([ + 'success' => false, + 'message' => "Камера {$id}: credentials не настроены", + 'hint' => "Установите CAMERA_{$id}_LOGIN и CAMERA_{$id}_PASSWORD в .env файле", + 'data' => [ + 'camera_id' => $id, + 'login_set' => !empty($login), + 'password_set' => !empty($password), + ], + ]); + } + + try { + $client = new Client(['timeout' => 15]); + $startTime = microtime(true); + + // Авторизация в API Дом.ру + $response = $client->post('https://vs.domru.ru/api/Login', [ + 'form_params' => [ + 'Login' => $login, + 'Password' => $password, + ], + 'verify' => false, + ]); + + $data = json_decode($response->getBody()->getContents(), true); + $sessionId = $data['SessionID'] ?? null; + + if (empty($sessionId)) { + return $this->asJson([ + 'success' => false, + 'message' => "Камера {$id}: ошибка авторизации", + 'error' => $data['Error'] ?? 'Не получен SessionID', + 'data' => [ + 'camera_id' => $id, + 'login' => $this->maskValue("CAMERA_{$id}_LOGIN", $login), + ], + ]); + } + + // Получение списка камер + $camerasResponse = $client->post('https://vs.domru.ru/api/GetCameras', [ + 'form_params' => [ + 'SessionID' => $sessionId, + ], + 'verify' => false, + ]); + + $camerasData = json_decode($camerasResponse->getBody()->getContents(), true); + $duration = round((microtime(true) - $startTime) * 1000, 2); + + $camerasList = []; + if (is_array($camerasData)) { + foreach ($camerasData as $camera) { + $camerasList[] = [ + 'name' => $camera['Name'] ?? 'N/A', + 'account_object' => $camera['AccountObjectName'] ?? 'N/A', + 'camera_id' => $camera['CameraID'] ?? 'N/A', + ]; + } + } + + return $this->asJson([ + 'success' => true, + 'message' => "Камера {$id}: подключение успешно", + 'data' => [ + 'camera_id' => $id, + 'login' => $this->maskValue("CAMERA_{$id}_LOGIN", $login), + 'session_id' => substr($sessionId, 0, 8) . '...', + 'cameras_count' => count($camerasList), + 'cameras' => $camerasList, + 'duration_ms' => $duration, + ], + ]); + } catch (GuzzleException $e) { + return $this->asJson([ + 'success' => false, + 'message' => "Камера {$id}: ошибка подключения", + 'error' => $e->getMessage(), + 'data' => [ + 'camera_id' => $id, + 'login' => $this->maskValue("CAMERA_{$id}_LOGIN", $login), + ], + ]); + } + } + + /** + * Тестирование всех настроенных камер Дом.ру + */ + public function actionTestAllCameras(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + $results = []; + $successCount = 0; + $failCount = 0; + $skippedCount = 0; + + for ($id = 1; $id <= 6; $id++) { + $login = getenv("CAMERA_{$id}_LOGIN") ?: ''; + $password = getenv("CAMERA_{$id}_PASSWORD") ?: ''; + + if (empty($login) || empty($password)) { + $results["camera_{$id}"] = [ + 'success' => null, + 'message' => 'Не настроена', + 'skipped' => true, + ]; + $skippedCount++; + continue; + } + + // Вызываем тест для каждой камеры + $testResult = $this->actionTestCamera($id); + $testData = json_decode($testResult->content, true); + $results["camera_{$id}"] = $testData; + + if ($testData['success'] ?? false) { + $successCount++; + } else { + $failCount++; + } + + // Небольшая пауза между запросами + usleep(500000); // 0.5 секунды + } + + return $this->asJson([ + 'success' => $failCount === 0 && $successCount > 0, + 'message' => $successCount > 0 + ? "Камеры: {$successCount} успешно, {$failCount} ошибок, {$skippedCount} пропущено" + : 'Нет настроенных камер', + 'summary' => [ + 'total' => 6, + 'configured' => $successCount + $failCount, + 'passed' => $successCount, + 'failed' => $failCount, + 'skipped' => $skippedCount, + ], + 'results' => $results, + ]); + } + /** * Полная проверка всех переменных окружения */ @@ -627,7 +792,7 @@ class DiagnosticController extends Controller ]; $cameras = []; - for ($i = 1; $i <= 5; $i++) { + for ($i = 1; $i <= 6; $i++) { $cameras["CAMERA_{$i}_LOGIN"] = "Логин камеры $i"; $cameras["CAMERA_{$i}_PASSWORD"] = "Пароль камеры $i"; } @@ -673,7 +838,7 @@ class DiagnosticController extends Controller */ private function getConnectionsStatus(): array { - return [ + $connections = [ 'postgresql' => [ 'name' => 'PostgreSQL (основная БД)', 'configured' => Yii::$app->has('db'), @@ -703,6 +868,18 @@ class DiagnosticController extends Controller 'configured' => Yii::$app->has('queue'), ], ]; + + // Добавляем статус камер + for ($i = 1; $i <= 6; $i++) { + $loginSet = !empty(getenv("CAMERA_{$i}_LOGIN")); + $passwordSet = !empty(getenv("CAMERA_{$i}_PASSWORD")); + $connections["camera_{$i}"] = [ + 'name' => "Камера Дом.ру #{$i}", + 'configured' => $loginSet && $passwordSet, + ]; + } + + return $connections; } /** diff --git a/erp24/views/diagnostic/index.php b/erp24/views/diagnostic/index.php index d94f4b77..4fb25b1a 100644 --- a/erp24/views/diagnostic/index.php +++ b/erp24/views/diagnostic/index.php @@ -583,6 +583,49 @@ $this->params['breadcrumbs'][] = $this->title; + +
+
+

📹 Камеры Дом.ру

+ +
+
+ + +
+
+ +
+
+
Камера
+
+ + Сконфигурирована + + Не настроена + +
+
+
+ +
+
+
+ + + +
+
+
+
@@ -705,7 +748,9 @@ $this->params['breadcrumbs'][] = $this->title; 'telegram-salebot': '', 'queue': '', 'send-message': '', - 'run-all': '' + 'run-all': '', + 'camera': '', + 'all-cameras': '' }; async function testConnection(type) { @@ -845,6 +890,99 @@ $this->params['breadcrumbs'][] = $this->title; } } + async function testCamera(cameraId) { + const resultContainer = document.getElementById('camera-' + cameraId + '-result'); + const statusContainer = document.getElementById('camera-' + cameraId + '-status'); + + resultContainer.innerHTML = '
Тестирование камеры ' + cameraId + '...'; + resultContainer.classList.add('show'); + + try { + const response = await fetch(endpoints['camera'] + '?id=' + cameraId); + const data = await response.json(); + + let statusBadge = data.success + ? '✓ Подключена' + : '✗ Ошибка'; + + if (data.skipped) { + statusBadge = 'Не настроена'; + } + + statusContainer.innerHTML = statusBadge; + + let resultHtml = `${data.message}
`; + if (data.data) { + // Форматируем вывод камер + if (data.data.cameras && data.data.cameras.length > 0) { + resultHtml += '
Камеры в аккаунте:
    '; + data.data.cameras.forEach(cam => { + resultHtml += `
  • ${cam.name} (${cam.account_object})
  • `; + }); + resultHtml += '
'; + + // Показываем остальные данные без массива камер + const displayData = {...data.data}; + delete displayData.cameras; + resultHtml += '
' + JSON.stringify(displayData, null, 2) + '
'; + } else { + resultHtml += '
' + JSON.stringify(data.data, null, 2) + '
'; + } + } + if (data.error) { + resultHtml += '
' + data.error + '
'; + } + if (data.hint) { + resultHtml += '

💡 ' + data.hint + '

'; + } + + resultContainer.innerHTML = resultHtml; + } catch (error) { + resultContainer.innerHTML = '
Ошибка запроса: ' + error.message + '
'; + statusContainer.innerHTML = '✗ Ошибка'; + } + } + + async function testAllCameras() { + const summaryContainer = document.getElementById('cameras-summary-result'); + summaryContainer.innerHTML = '
Тестирование всех камер...'; + summaryContainer.classList.add('show'); + + // Тестируем каждую камеру по очереди + for (let i = 1; i <= 6; i++) { + const loginSet = document.querySelector(`#camera-${i}-status .status-badge`)?.textContent?.includes('Сконфигурирована'); + if (loginSet || document.querySelector(`button[onclick="testCamera(${i})"]`)?.disabled === false) { + await testCamera(i); + await new Promise(resolve => setTimeout(resolve, 300)); + } + } + + // Получаем общую сводку + try { + const response = await fetch(endpoints['all-cameras']); + const data = await response.json(); + + let summaryHtml = '

📊 Сводка по камерам

'; + + if (data.summary) { + const { passed, failed, skipped, configured } = data.summary; + summaryHtml += ` +
+
${passed} успешно
+
${failed} ошибок
+
${skipped} пропущено
+
${configured} настроено
+
+ `; + } + + summaryHtml += `

${data.message}

`; + summaryContainer.innerHTML = summaryHtml; + } catch (error) { + summaryContainer.innerHTML = '
Ошибка: ' + error.message + '
'; + } + } + // Загрузка реальных названий ботов при открытии страницы async function loadBotNames() { // Загрузка Dev бота -- 2.39.5