diff --git a/app/app/Http/Controllers/Api/AdminDashboardController.php b/app/app/Http/Controllers/Api/AdminDashboardController.php index 758c947b..91641343 100644 --- a/app/app/Http/Controllers/Api/AdminDashboardController.php +++ b/app/app/Http/Controllers/Api/AdminDashboardController.php @@ -407,7 +407,7 @@ class AdminDashboardController extends Controller /** Известные сервисы плитки — фильтр от осиротевших строк (jivosite и пр.). */ private const KNOWN_SERVICE_KEYS = [ 'dadata', 'supplier', 'yandex_cloud', 'email', 'yookassa', 'captcha', - 'aitunnel', 'exa', 'xfetch', 'self_render', 'sales_finder', 'exa_tunnel', + 'aitunnel', 'exa', 'xfetch', 'keyso', 'self_render', 'sales_finder', 'exa_tunnel', ]; /** diff --git a/app/app/Services/External/ExternalBalanceRefresher.php b/app/app/Services/External/ExternalBalanceRefresher.php index aa69b607..d3a8080b 100644 --- a/app/app/Services/External/ExternalBalanceRefresher.php +++ b/app/app/Services/External/ExternalBalanceRefresher.php @@ -35,7 +35,7 @@ class ExternalBalanceRefresher /** * Реестр: service_key => [kind, heavy, class]. kind: 'balance' | 'liveness'. - * heavy=true только у supplier (робот-браузер). 11 сервисов (Task 9, 16.07.2026). + * heavy=true только у supplier (робот-браузер). 13 ключей (16.07.2026: +keyso, xfetch→liveness). * * @return array */ @@ -54,6 +54,7 @@ class ExternalBalanceRefresher 'self_render' => ['kind' => 'liveness', 'heavy' => false, 'class' => SelfRenderLivenessProbe::class], 'sales_finder' => ['kind' => 'liveness', 'heavy' => false, 'class' => SalesFinderLivenessProbe::class], 'exa_tunnel' => ['kind' => 'liveness', 'heavy' => false, 'class' => ExaTunnelLivenessProbe::class], + 'keyso' => ['kind' => 'liveness', 'heavy' => false, 'class' => KeysoLivenessProbe::class], ]; } @@ -221,6 +222,7 @@ class ExternalBalanceRefresher app(SalesFinderLivenessProbe::class), app(ExaTunnelLivenessProbe::class), app(XfetchLivenessProbe::class), + app(KeysoLivenessProbe::class), ]; } diff --git a/app/app/Services/External/KeysoLivenessProbe.php b/app/app/Services/External/KeysoLivenessProbe.php new file mode 100644 index 00000000..d97939af --- /dev/null +++ b/app/app/Services/External/KeysoLivenessProbe.php @@ -0,0 +1,37 @@ +get(rtrim($base, '/').'/'); + + return $resp->status() < 500 + ? LivenessReading::alive('keyso', 'keys.so отвечает') + : LivenessReading::down('keyso', 'HTTP '.$resp->status()); + } catch (\Throwable $e) { + return LivenessReading::down('keyso', $e->getMessage()); + } + } +} diff --git a/app/config/services.php b/app/config/services.php index e13f10a8..7db0dc2b 100644 --- a/app/config/services.php +++ b/app/config/services.php @@ -270,4 +270,11 @@ return [ 'health_path' => env('SALES_FINDER_HEALTH_PATH', ''), ], + // Плитка балансов (16.07): Keyso (keys.so) — разведка рекламы, зависимость службы + // «Поиск клиентов» (/opt/sales-finder держит токен). Портал только проверяет живость + // хоста (пинг api.keys.so, <500 = жив), денег/токена не нужно. + 'keyso' => [ + 'base_url' => env('KEYSO_BASE_URL', 'https://api.keys.so'), + ], + ]; diff --git a/app/tests/Unit/External/ExternalBalanceRefresherTest.php b/app/tests/Unit/External/ExternalBalanceRefresherTest.php index bd7d59f3..e06fae1a 100644 --- a/app/tests/Unit/External/ExternalBalanceRefresherTest.php +++ b/app/tests/Unit/External/ExternalBalanceRefresherTest.php @@ -101,23 +101,23 @@ it('sendAlerts=true шлёт письмо на переход green→red (св Mail::assertSent(ExternalServiceDownMail::class, 1); }); -it('allKeys содержит 12 сервисов и refresh(allKeys) их пишет', function () { +it('allKeys содержит 13 сервисов и refresh(allKeys) их пишет', function () { config()->set('services.aitunnel.key', 'k'); config()->set('services.aitunnel.balance_url', ''); config()->set('services.exa.key', 'k'); config()->set('services.exa.balance_url', ''); - config()->set('services.xfetch.key', 'k'); - config()->set('services.xfetch.balance_url', ''); + config()->set('services.xfetch.endpoint', 'https://xf4.ru/fetch'); + config()->set('services.keyso.base_url', 'https://api.keys.so'); config()->set('services.self_render.health_url', 'https://vm.local/h'); config()->set('services.sales_finder.base_url', 'https://lk.liderra.ru/finder/'); config()->set('services.exa.proxy', 'socks5h://127.0.0.1:10808'); Http::fake(['*' => Http::response('ok', 200)]); $refresher = app(ExternalBalanceRefresher::class); - expect($refresher->allKeys())->toHaveCount(12); + expect($refresher->allKeys())->toHaveCount(13); $refresher->refresh($refresher->allKeys(), force: true, sendAlerts: false); $keys = DB::connection('pgsql_supplier')->table('external_service_balances')->pluck('service_key'); - foreach (['aitunnel', 'exa', 'xfetch', 'self_render', 'sales_finder', 'exa_tunnel'] as $k) { + foreach (['aitunnel', 'exa', 'xfetch', 'keyso', 'self_render', 'sales_finder', 'exa_tunnel'] as $k) { expect($keys)->toContain($k); } }); diff --git a/app/tests/Unit/External/KeysoLivenessProbeTest.php b/app/tests/Unit/External/KeysoLivenessProbeTest.php new file mode 100644 index 00000000..4a409375 --- /dev/null +++ b/app/tests/Unit/External/KeysoLivenessProbeTest.php @@ -0,0 +1,36 @@ + config()->set('services.keyso.base_url', 'https://api.keys.so')); + +it('зелёный при 200', function () { + Http::fake(['api.keys.so/*' => Http::response('ok', 200)]); + expect((new KeysoLivenessProbe)->check()->light)->toBe('green'); +}); + +it('зелёный при 404 (хост достучались — keys.so жив)', function () { + Http::fake(['api.keys.so/*' => Http::response('Not Found', 404)]); + expect((new KeysoLivenessProbe)->check()->light)->toBe('green'); +}); + +it('красный при 5xx и при обрыве', function () { + Http::fake(['api.keys.so/*' => Http::response('err', 503)]); + expect((new KeysoLivenessProbe)->check()->light)->toBe('red'); + Http::fake(fn () => throw new RuntimeException('down')); + expect((new KeysoLivenessProbe)->check()->light)->toBe('red'); +}); + +it('серый, когда base_url пуст', function () { + config()->set('services.keyso.base_url', ''); + expect((new KeysoLivenessProbe)->check()->light)->toBe('grey'); +}); + +it('serviceKey = keyso', function () { + expect((new KeysoLivenessProbe)->serviceKey())->toBe('keyso'); +}); diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index ee644000..905ba6e2 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-07-16T13:56:25.750Z +Last updated: 2026-07-16T14:00:21.940Z | Контролёр | Состояние | Детали | |---|---|---| @@ -112,8 +112,8 @@ Episodes since last run: 542 / threshold: 10 | PID | Имя | CPU-время | Возраст | |---|---|---|---| -| 3488 | MsMpEng | 10.45ч | NaNч | -| 9756 | Code | 5.90ч | NaNч | +| 3488 | MsMpEng | 10.48ч | NaNч | +| 9756 | Code | 5.92ч | 14477104.5ч | | 1320 | svchost | 2.35ч | 0.0ч | ⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий.