fox/app/Http/Controllers/FoxStatusController.php

148 lines
4.8 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Services\SystemStatsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
/**
* Voller System-Statusbericht für die HUD-Diagnose-Animation.
* Erweitert SystemStatsService um Service-Pings (Reverb, Google) + Uptime.
*/
class FoxStatusController
{
public function __invoke(SystemStatsService $stats): JsonResponse
{
$base = $stats->collect();
return response()->json([
'system' => [
'cpu' => $base['cpu'],
'memory' => $base['memory'],
'disk' => $base['disk'],
'load' => $base['load'],
'uptime' => $this->uptime(),
],
'services' => [
'ollama' => $base['ollama'],
'openai' => $base['openai'],
'reverb' => $this->reverbStatus(),
'google_directions' => $this->googleApiStatus('directions'),
'google_places' => $this->googleApiStatus('places'),
'google_geocoding' => $this->googleApiStatus('geocoding'),
'searxng' => $this->searxStatus(),
'queue' => ['len' => $base['queue']],
],
'fox' => [
'version' => '0.9.0',
'mode' => config('services.tts.engine', 'piper'),
'voice' => config('services.openai.tts_voice', 'onyx'),
'last_action' => $this->lastActionMeta(),
],
'timestamp' => now()->toIso8601String(),
]);
}
protected function uptime(): array
{
$raw = (string) @file_get_contents('/proc/uptime');
$secs = (float) explode(' ', trim($raw))[0];
return [
'seconds' => (int) $secs,
'human' => $this->humanDuration((int) $secs),
];
}
protected function humanDuration(int $secs): string
{
$d = intdiv($secs, 86400);
$h = intdiv($secs % 86400, 3600);
$m = intdiv($secs % 3600, 60);
return ($d ? $d.'d ' : '').($h ? $h.'h ' : '').$m.'m';
}
protected function reverbStatus(): array
{
try {
$r = Http::timeout(2)->get('http://reverb:8080/');
return ['online' => $r->status() < 500];
} catch (\Throwable) {
return ['online' => false];
}
}
protected function googleApiStatus(string $api): array
{
$key = config('services.google.maps_key');
if (empty($key)) {
return ['online' => false, 'reason' => 'no_key'];
}
// Cache 60s damit jeder Status-Call nicht alle APIs pingt.
return Cache::remember('fox.google.'.$api, 60, function () use ($api, $key) {
try {
if ($api === 'directions') {
$r = Http::timeout(3)->get('https://maps.googleapis.com/maps/api/directions/json', [
'origin' => '48.2082,16.3738', 'destination' => '48.21,16.37', 'key' => $key,
]);
$status = $r->json('status');
return ['online' => $r->successful() && in_array($status, ['OK', 'ZERO_RESULTS'], true)];
}
if ($api === 'geocoding') {
$r = Http::timeout(3)->get('https://maps.googleapis.com/maps/api/geocode/json', [
'latlng' => '48.2082,16.3738', 'key' => $key,
]);
return ['online' => $r->successful() && $r->json('status') === 'OK'];
}
if ($api === 'places') {
$r = Http::timeout(3)
->withHeaders(['X-Goog-Api-Key' => $key, 'X-Goog-FieldMask' => 'places.id'])
->post('https://places.googleapis.com/v1/places:searchText', [
'textQuery' => 'wien', 'maxResultCount' => 1,
]);
return ['online' => $r->successful()];
}
return ['online' => false];
} catch (\Throwable) {
return ['online' => false];
}
});
}
protected function searxStatus(): array
{
try {
$r = Http::timeout(2)->get(rtrim((string) config('services.searxng.url'), '/').'/search', [
'q' => 'test', 'format' => 'json',
]);
return ['online' => $r->status() < 500];
} catch (\Throwable) {
return ['online' => false];
}
}
protected function lastActionMeta(): ?array
{
$cached = Cache::get('fox.last_pois');
if (! is_array($cached)) {
return null;
}
return [
'type' => 'poi_search',
'label' => $cached['label'] ?? null,
'count' => count($cached['pois'] ?? []),
];
}
}