diff --git a/app/Livewire/Project/Application/TrafficOverview.php b/app/Livewire/Project/Application/TrafficOverview.php
deleted file mode 100644
index 6cc30605a0..0000000000
--- a/app/Livewire/Project/Application/TrafficOverview.php
+++ /dev/null
@@ -1,86 +0,0 @@
-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]);
- [$from, $to] = SentinelTrafficClient::rangeWindow('24h');
- // Compose services and previews record under their own keys. Sentinel's resource
- // scope merges them exactly; an older Sentinel gets every key summed here.
- $overview = $client->tryResourceOverview($this->application->uuid, $from, $to);
- if ($overview === null) {
- $keys = $client->resourceKeys($this->application->uuid);
- if (count($keys) > 1) {
- $client->prefetchAppOverviews($keys, $from, $to);
- }
- $overviews = array_map(fn (string $key) => $client->overview($key, $from, $to), $keys);
- $overview = TrafficAnalyticsAggregator::sumOverviews($overviews)['overview'];
- }
- $this->overview = $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'
-
- HTML;
- }
-
- public function render()
- {
- return view('livewire.project.application.traffic-overview');
- }
-}
diff --git a/resources/views/livewire/project/application/traffic-overview.blade.php b/resources/views/livewire/project/application/traffic-overview.blade.php
deleted file mode 100644
index 831a372ec2..0000000000
--- a/resources/views/livewire/project/application/traffic-overview.blade.php
+++ /dev/null
@@ -1,66 +0,0 @@
-@php
- $analyticsRoute = route('project.application.analytics', [
- 'project_uuid' => $application->environment->project->uuid,
- 'environment_uuid' => $application->environment->uuid,
- 'application_uuid' => $application->uuid,
- ]);
-@endphp
-
-
- @if (! $enabled)
- @if ($eligible && $serverUuid)
-
-
-
-
Traffic analytics
-
- Enable Sentinel traffic analytics on this server to see requests, visitors, and
- geography for this application. Restarts the proxy + Sentinel.
-
-
-
- Server settings
-
-
-
-
- @endif
- @else
-
-
-
- @if (! $this->hasData())
-
- No traffic recorded in the last 24h yet.
-
- @else
-
-
- Requests
- {{ number_format($overview['requests'] ?? 0) }}
-
-
- Unique visitors
- {{ number_format($overview['uniqueVisitors'] ?? 0) }}
-
-
- Error rate
- {{ $this->errorRate() }}%
-
-
- p95 latency
- {{ number_format($overview['latencyP95'] ?? 0, 1) }} ms
-
-
- @endif
-
- @endif
-
diff --git a/tests/Feature/TrafficAnalytics/ApplicationTrafficOverviewTest.php b/tests/Feature/TrafficAnalytics/ApplicationTrafficOverviewTest.php
deleted file mode 100644
index 4c958f1a79..0000000000
--- a/tests/Feature/TrafficAnalytics/ApplicationTrafficOverviewTest.php
+++ /dev/null
@@ -1,121 +0,0 @@
-responses as $needle => $response) {
- if (str_contains($url, $needle)) {
- return $response;
- }
- }
-
- return '{}';
- }
-}
-
-beforeEach(function () {
- // Servers are memoized via once()/cache; clear both so DB-id reuse across tests doesn't bleed a stale enabled server.
- Cache::flush();
- Server::flushIdentityMap();
- $this->team = Team::factory()->create();
- $this->user = User::factory()->create();
- $this->team->members()->attach($this->user->id, ['role' => 'owner']);
- $this->actingAs($this->user);
- session(['currentTeam' => $this->team]);
- $this->privateKey = PrivateKey::factory()->create(['team_id' => $this->team->id]);
-});
-
-function makeAppOnServer(bool $analyticsEnabled): Application
-{
- $server = Server::factory()->create([
- 'team_id' => test()->team->id,
- 'private_key_id' => test()->privateKey->id,
- ]);
- $server->settings->is_traffic_analytics_enabled = $analyticsEnabled;
- $server->settings->save();
-
- $project = Project::factory()->create(['team_id' => test()->team->id]);
- $environment = Environment::factory()->create(['project_id' => $project->id]);
- $destination = StandaloneDocker::where('server_id', $server->id)->first()
- ?? StandaloneDocker::factory()->create(['server_id' => $server->id, 'network' => 'coolify-test']);
-
- return Application::factory()->create([
- 'name' => 'Widget App',
- 'environment_id' => $environment->id,
- 'destination_id' => $destination->id,
- 'destination_type' => StandaloneDocker::class,
- ]);
-}
-
-it('shows last-24h KPIs and a link to full analytics when enabled with data', function () {
- $application = makeAppOnServer(true);
-
- app()->bind(SentinelTrafficClient::class, function ($app, $params) {
- $client = new FakeAppOverviewTrafficClient($params['server']);
- $client->responses = [
- '/traffic/overview' => json_encode([
- 'requests' => 4200,
- 'bytes_in' => 5000,
- 'bytes_out' => 25000,
- 'status' => ['s2xx' => 4000, 's3xx' => 100, 's4xx' => 80, 's5xx' => 20],
- 'latency' => ['p50' => 12.5, 'p95' => 45.2, 'p99' => 90.1],
- 'unique_visitors' => 1234,
- ]),
- ];
-
- return $client;
- });
-
- $component = loadLazy(Livewire::test(TrafficOverview::class, ['application' => $application]));
- $component->assertOk()
- ->assertSet('enabled', true)
- ->assertSee('Traffic (last 24h)')
- ->assertSee('4,200')
- ->assertSee('1,234')
- ->assertSee('View full analytics');
-});
-
-it('shows the muted no-data note when enabled but no traffic recorded', function () {
- $application = makeAppOnServer(true);
-
- app()->bind(SentinelTrafficClient::class, function ($app, $params) {
- $client = new FakeAppOverviewTrafficClient($params['server']);
- $client->responses = ['/traffic/overview' => json_encode(['requests' => 0])];
-
- return $client;
- });
-
- loadLazy(Livewire::test(TrafficOverview::class, ['application' => $application]))
- ->assertOk()
- ->assertSee('No traffic recorded in the last 24h yet');
-});
-
-it('shows the enable nudge when analytics is disabled on an eligible server', function () {
- $application = makeAppOnServer(false);
-
- loadLazy(Livewire::test(TrafficOverview::class, ['application' => $application]))
- ->assertOk()
- ->assertSet('enabled', false)
- ->assertSee('Traffic analytics')
- ->assertSee('Server settings')
- ->assertDontSee('Traffic (last 24h)');
-});
diff --git a/tests/Feature/TrafficAnalytics/ResourceScopeTrafficTest.php b/tests/Feature/TrafficAnalytics/ResourceScopeTrafficTest.php
index 36ef965c08..2b6f6589e7 100644
--- a/tests/Feature/TrafficAnalytics/ResourceScopeTrafficTest.php
+++ b/tests/Feature/TrafficAnalytics/ResourceScopeTrafficTest.php
@@ -2,7 +2,6 @@
use App\Livewire\Analytics as GlobalAnalytics;
use App\Livewire\Project\Application\Analytics as ApplicationAnalytics;
-use App\Livewire\Project\Application\TrafficOverview;
use App\Livewire\Project\Service\Analytics as ServiceAnalytics;
use App\Models\Application;
use App\Models\Environment;
@@ -369,31 +368,11 @@ it('falls back to the per-key merge with approximate badges when Sentinel lacks
// The absence is cached: a refresh within the ttl goes straight to the per-key path.
$component->call('loadData')->assertSet('uniquesApproximate', true);
- loadLazy(Livewire::test(TrafficOverview::class, ['application' => $this->application]))->assertOk();
+ loadLazy(Livewire::test(ApplicationAnalytics::class, ['application' => $this->application]))->assertOk();
expect(requestedResourceScopeUrls('/resource/'))->toHaveCount($probes);
});
-it('shows the resource overview in the application traffic card', function () {
- bindResourceScopeFake(resourceScopeResponses($this->application->uuid, resourceScopeOverview(4200, 33)));
-
- $component = loadLazy(Livewire::test(TrafficOverview::class, ['application' => $this->application]))
- ->assertOk()
- ->assertSee('4,200');
-
- expect($component->instance()->overview['uniqueVisitors'])->toBe(33)
- ->and(requestedResourceScopeUrls('/resource/'))->toHaveCount(1)
- ->and(requestedResourceScopeUrls('/traffic/dashboard'))->toBeEmpty();
-});
-
-it('sums the keys in the application traffic card when Sentinel lacks the resource routes', function () {
- bindResourceScopeFake(perKeyFallbackResponses($this->application->uuid, $this->apiKey, $this->webKey));
-
- loadLazy(Livewire::test(TrafficOverview::class, ['application' => $this->application]))
- ->assertOk()
- ->assertSee('150');
-});
-
it('uses the resource scope when the global analytics filter selects a resource', function () {
bindResourceScopeFake([
...resourceScopeResponses($this->application->uuid, resourceScopeOverview(150, 12)),
diff --git a/tests/Feature/TrafficAnalytics/ResourceTrafficKeysTest.php b/tests/Feature/TrafficAnalytics/ResourceTrafficKeysTest.php
index a2d904796b..4b2f6ce84e 100644
--- a/tests/Feature/TrafficAnalytics/ResourceTrafficKeysTest.php
+++ b/tests/Feature/TrafficAnalytics/ResourceTrafficKeysTest.php
@@ -6,7 +6,6 @@ use App\Data\Traffic\TrafficPathData;
use App\Data\Traffic\TrafficSeriesBucketData;
use App\Livewire\Analytics as GlobalAnalytics;
use App\Livewire\Project\Application\Analytics as ApplicationAnalytics;
-use App\Livewire\Project\Application\TrafficOverview;
use App\Models\Application;
use App\Models\Environment;
use App\Models\PrivateKey;
@@ -286,7 +285,7 @@ it('shows a compose application with the data of all its compose service keys',
->and($paths['/checkout']['domain'])->toBe('www.shop.test');
});
-it('sums every compose key in the application traffic card', function () {
+it('sums every compose key on the application analytics page', function () {
$application = makeTrafficComposeApplication();
$apiKey = $application->uuid.'-'.traefikSafeServiceNameSegment('api');
$webKey = $application->uuid.'-'.traefikSafeServiceNameSegment('web');
@@ -297,7 +296,7 @@ it('sums every compose key in the application traffic card', function () {
"/app/{$webKey}/traffic/overview" => resourceKeysOverview(200),
]);
- loadLazy(Livewire::test(TrafficOverview::class, ['application' => $application]))
+ loadLazy(Livewire::test(ApplicationAnalytics::class, ['application' => $application]))
->assertOk()
->assertSee('4,200');
});