Files
filament-short-url/src/Filament/Resources/ShortUrlResource/Widgets/ShortUrlVisitsBottomBreakdown.php
Bartłomiej Janczak eda4794cac feat: v1.2.0 — password protection, warning pages, smart targeting, rate limiting, daily stats aggregation
- Add password-protected links with session-based unlock flow
- Add redirect warning (interstitial) pages before external URLs
- Add smart targeting rules: device-based, country/geo, A/B weighted rotation
- Add configurable per-IP rate limiting with 429 + Retry-After headers
- Add daily stats aggregation & log pruning (short-url:aggregate-and-prune)
- Add IncrementVisitJob as queue-based counter buffering fallback
- Add ShortUrlDailyStats model with JSON stat columns per day
- Add two new migrations: targeting/security fields, daily_stats table
- Add password-prompt.blade.php and warning.blade.php views
- Extend Settings GUI with Performance & Security tab (aggregation + rate limiting)
- Extend ShortUrlForm with Targeting & Security section
- Add POST route for password form submission (was GET-only → 405)
- Replace enum(device_type) with string(20) for cross-DB compatibility
- Remove ->after() MySQL-only hints from ALTER TABLE migrations
- Fix aggregation test: use whereDate() instead of assertDatabaseHas for date column
- Extend en/pl translations for all new features
- Expand README.md with full v1.2.0 documentation (476 lines)
2026-06-01 13:54:13 +02:00

59 lines
1.7 KiB
PHP

<?php
namespace Bjanczak\FilamentShortUrl\Filament\Resources\ShortUrlResource\Widgets;
use Bjanczak\FilamentShortUrl\Models\ShortUrl;
use Filament\Widgets\Widget;
class ShortUrlVisitsBottomBreakdown extends Widget
{
public ?ShortUrl $record = null;
public ?string $dateFrom = null;
public ?string $dateTo = null;
protected string $view = 'filament-short-url::widgets.visits-bottom-breakdown';
protected int|string|array $columnSpan = 'full';
public function mount(?ShortUrl $record = null): void
{
$this->record = $record;
}
/**
* @return array<string, mixed>
*/
protected function getViewData(): array
{
if (! $this->record) {
return [
'visitsByDevice' => [],
'visitsByBrowser' => [],
'visitsByOs' => [],
'visitsByReferer' => [],
'visitsByCity' => [],
'utmSources' => [],
'utmMediums' => [],
'utmCampaigns' => [],
'totalVisits' => 0,
];
}
$stats = $this->record->getCachedStats($this->dateFrom, $this->dateTo);
return [
'visitsByDevice' => $stats['visitsByDevice'] ?? [],
'visitsByBrowser' => $stats['visitsByBrowser'] ?? [],
'visitsByOs' => $stats['visitsByOs'] ?? [],
'visitsByReferer' => $stats['visitsByReferer'] ?? [],
'visitsByCity' => $stats['visitsByCity'] ?? [],
'utmSources' => $stats['utmSources'] ?? [],
'utmMediums' => $stats['utmMediums'] ?? [],
'utmCampaigns' => $stats['utmCampaigns'] ?? [],
'totalVisits' => $stats['totalVisits'] ?? 0,
];
}
}