mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-08 06:56:02 -04:00
Traffic analytics now covers every resource on both proxies:
- Caddy: Compose applications and services get access-log labels. The
log_append key is {uuid}-{service}, the same key Sentinel reads from
Traefik router names.
- A resource owns the Sentinel key {uuid} and every key that starts
with {uuid}-. Application pages and the traffic card now merge all
keys, so Compose applications and previews show data on Traefik too.
- Global analytics: the leaderboard groups the keys of one resource
into one row, lists services, and never names keys of other teams.
- New service analytics page (project.service.analytics) with the same
view and authorization as the application page.
- Switching the proxy type restarts Sentinel when analytics is on, so
it reads the new log path. Sentinel gets no traffic mount when the
proxy has no analytics support.
- Caddy servers show a note that resources log only after a redeploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
2.7 KiB
PHP
82 lines
2.7 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Project\Application;
|
|
|
|
use App\Models\Application;
|
|
use App\Services\SentinelTrafficClient;
|
|
use App\Services\TrafficAnalyticsAggregator;
|
|
use Livewire\Attributes\Lazy;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* Compact last-24h traffic KPI card for the application General page. Lazy-loaded so
|
|
* the General page isn't blocked by Sentinel's docker-exec round-trip.
|
|
*/
|
|
#[Lazy]
|
|
class TrafficOverview extends Component
|
|
{
|
|
public Application $application;
|
|
|
|
public bool $enabled = false;
|
|
|
|
public bool $eligible = false;
|
|
|
|
public ?string $serverUuid = null;
|
|
|
|
public ?array $overview = null;
|
|
|
|
public function mount(): void
|
|
{
|
|
// Runs in the deferred lazy-load request, so the Sentinel fetch never blocks
|
|
// the initial General-page render.
|
|
$server = $this->application->destination?->server;
|
|
$this->serverUuid = $server?->uuid;
|
|
$this->enabled = (bool) $server?->isTrafficAnalyticsEnabled();
|
|
$this->eligible = $server ? (! $server->isSwarm() && ! $server->isBuildServer()) : false;
|
|
|
|
if ($this->enabled && $server) {
|
|
try {
|
|
$client = app(SentinelTrafficClient::class, ['server' => $server]);
|
|
// Compose services and previews record under their own keys; sum all of them.
|
|
$keys = $client->resourceKeys($this->application->uuid);
|
|
[$from, $to] = SentinelTrafficClient::rangeWindow('24h');
|
|
if (count($keys) > 1) {
|
|
$client->prefetchAppOverviews($keys, $from, $to);
|
|
}
|
|
$overviews = array_map(fn (string $key) => $client->overview($key, $from, $to), $keys);
|
|
$this->overview = TrafficAnalyticsAggregator::sumOverviews($overviews)['overview']->toArray();
|
|
} catch (\Throwable $e) {
|
|
$this->overview = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function hasData(): bool
|
|
{
|
|
return $this->overview !== null && (int) ($this->overview['requests'] ?? 0) > 0;
|
|
}
|
|
|
|
public function errorRate(): float
|
|
{
|
|
if (! $this->overview || (int) ($this->overview['requests'] ?? 0) === 0) {
|
|
return 0.0;
|
|
}
|
|
|
|
$errors = (int) ($this->overview['s4xx'] ?? 0) + (int) ($this->overview['s5xx'] ?? 0);
|
|
|
|
return round(($errors / $this->overview['requests']) * 100, 2);
|
|
}
|
|
|
|
public function placeholder(): string
|
|
{
|
|
return <<<'HTML'
|
|
<div class="h-24 w-full animate-pulse rounded-xl border border-neutral-200 bg-neutral-50 dark:border-white/[0.08] dark:bg-white/[0.02]"></div>
|
|
HTML;
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.project.application.traffic-overview');
|
|
}
|
|
}
|