Browse Source

Analytics, continued

imwald
Nuša Pukšič 3 months ago
parent
commit
711e0c2402
  1. 1
      config/packages/security.yaml
  2. 61
      src/Controller/Administration/VisitorAnalyticsController.php
  3. 32
      src/Security/CustomAccessDeniedHandler.php
  4. 58
      templates/admin/analytics.html.twig
  5. 10
      templates/bundles/TwigBundle/Exception/error403.html.twig

1
config/packages/security.yaml

@ -17,6 +17,7 @@ security: @@ -17,6 +17,7 @@ security:
logout:
path: /logout
entry_point: App\Security\NostrAuthenticator
access_denied_handler: App\Security\CustomAccessDeniedHandler
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall

61
src/Controller/Administration/VisitorAnalyticsController.php

@ -17,32 +17,32 @@ class VisitorAnalyticsController extends AbstractController @@ -17,32 +17,32 @@ class VisitorAnalyticsController extends AbstractController
public function index(VisitRepository $visitRepository): Response
{
// Counters for the last 24 hours and last 7 days
$last24h = new \DateTimeImmutable('-24 hours');
$last7d = new \DateTimeImmutable('-7 days');
$visitsLast24Hours = $visitRepository->countVisitsSince(new \DateTimeImmutable('-24 hours'));
$visitsLast7Days = $visitRepository->countVisitsSince(new \DateTimeImmutable('-7 days'));
$visitStats = $visitRepository->getVisitCountByRoute($last7d);
// Visits by route for the last 7 days
$routeVisitCountsLast7Days = $visitRepository->getVisitCountByRoute(new \DateTimeImmutable('-7 days'));
$last24hCount = $visitRepository->countVisitsSince($last24h);
$last7dCount = $visitRepository->countVisitsSince($last7d);
// Unique session tracking
$uniqueVisitors24h = $visitRepository->countUniqueSessionsSince($last24h);
$uniqueVisitors7d = $visitRepository->countUniqueSessionsSince($last7d);
// Unique visitors
$uniqueVisitorsLast24Hours = $visitRepository->countUniqueSessionsSince(new \DateTimeImmutable('-24 hours'));
$uniqueVisitorsLast7Days = $visitRepository->countUniqueSessionsSince(new \DateTimeImmutable('-7 days'));
$totalUniqueVisitors = $visitRepository->countUniqueVisitors();
// Session-based visit breakdown
$sessionStats = $visitRepository->getVisitsBySession($last7d);
// Visits by session for the last 7 days
$visitsBySessionLast7Days = $visitRepository->getVisitsBySession(new \DateTimeImmutable('-7 days'));
// New metrics for improved dashboard
// Summary metrics
$totalVisits = $visitRepository->getTotalVisits();
$avgVisitsPerSession = $visitRepository->getAverageVisitsPerSession();
$averageVisitsPerSession = $visitRepository->getAverageVisitsPerSession();
$bounceRate = $visitRepository->getBounceRate();
$visitsPerDay = $visitRepository->getVisitsPerDay(30);
$mostPopularRoutes = $visitRepository->getMostPopularRoutes(5);
$recentVisits = $visitRepository->getRecentVisits(10);
// Calculate unique visitors per day for the last 7 days
$uniqueVisitorsPerDay = [];
// Time series and top metrics
$dailyVisitCountsLast30Days = $visitRepository->getVisitsPerDay(30);
$topRoutesAllTime = $visitRepository->getMostPopularRoutes(5);
$recentVisitRecords = $visitRepository->getRecentVisits(10);
// Unique visitors per day for the last 7 days
$dailyUniqueVisitorCountsLast7Days = [];
for ($i = 6; $i >= 0; $i--) {
$day = (new \DateTimeImmutable("today"))->modify("-{$i} days");
$start = $day->setTime(0, 0, 0);
@ -54,28 +54,27 @@ class VisitorAnalyticsController extends AbstractController @@ -54,28 +54,27 @@ class VisitorAnalyticsController extends AbstractController
->setParameter('start', $start)
->setParameter('end', $end);
$count = (int) $qb->getQuery()->getSingleScalarResult();
$uniqueVisitorsPerDay[] = [
$dailyUniqueVisitorCountsLast7Days[] = [
'day' => $day->format('Y-m-d'),
'count' => $count
];
}
return $this->render('admin/analytics.html.twig', [
'visitStats' => $visitStats,
'last24hCount' => $last24hCount,
'last7dCount' => $last7dCount,
'uniqueVisitors24h' => $uniqueVisitors24h,
'uniqueVisitors7d' => $uniqueVisitors7d,
'routeVisitCountsLast7Days' => $routeVisitCountsLast7Days,
'visitsLast24Hours' => $visitsLast24Hours,
'visitsLast7Days' => $visitsLast7Days,
'uniqueVisitorsLast24Hours' => $uniqueVisitorsLast24Hours,
'uniqueVisitorsLast7Days' => $uniqueVisitorsLast7Days,
'totalUniqueVisitors' => $totalUniqueVisitors,
'sessionStats' => $sessionStats,
// New variables for dashboard
'visitsBySessionLast7Days' => $visitsBySessionLast7Days,
'totalVisits' => $totalVisits,
'avgVisitsPerSession' => $avgVisitsPerSession,
'averageVisitsPerSession' => $averageVisitsPerSession,
'bounceRate' => $bounceRate,
'visitsPerDay' => $visitsPerDay,
'mostPopularRoutes' => $mostPopularRoutes,
'recentVisits' => $recentVisits,
'uniqueVisitorsPerDay' => $uniqueVisitorsPerDay,
'dailyVisitCountsLast30Days' => $dailyVisitCountsLast30Days,
'topRoutesAllTime' => $topRoutesAllTime,
'recentVisitRecords' => $recentVisitRecords,
'dailyUniqueVisitorCountsLast7Days' => $dailyUniqueVisitorCountsLast7Days,
]);
}
}

32
src/Security/CustomAccessDeniedHandler.php

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
class CustomAccessDeniedHandler implements AccessDeniedHandlerInterface
{
private $twig;
private $router;
public function __construct(\Twig\Environment $twig, RouterInterface $router)
{
$this->twig = $twig;
$this->router = $router;
}
public function handle(Request $request, \Exception $accessDeniedException): ?Response
{
// If not logged in, redirect to login
if (!$request->getUser()) {
return new RedirectResponse($this->router->generate('app_login'));
}
// Otherwise, render a custom error page
$content = $this->twig->render('bundles/TwigBundle/Exception/error403.html.twig');
return new Response($content, 403);
}
}

58
templates/admin/analytics.html.twig

@ -10,8 +10,8 @@ @@ -10,8 +10,8 @@
<div class="analytics-card">
<h2>Total Visits</h2>
<ul class="analytics-stats">
<li><strong>Last 24 hours:</strong> {{ last24hCount }}</li>
<li><strong>Last 7 days:</strong> {{ last7dCount }}</li>
<li><strong>Last 24 hours:</strong> {{ visitsLast24Hours }}</li>
<li><strong>Last 7 days:</strong> {{ visitsLast7Days }}</li>
<li><strong>All time:</strong> {{ totalVisits }}</li>
</ul>
</div>
@ -19,28 +19,19 @@ @@ -19,28 +19,19 @@
<div class="analytics-card">
<h2>Unique Visitors</h2>
<ul class="analytics-stats">
<li><strong>Last 24 hours:</strong> {{ uniqueVisitors24h }}</li>
<li><strong>Last 7 days:</strong> {{ uniqueVisitors7d }}</li>
<li><strong>Last 24 hours:</strong> {{ uniqueVisitorsLast24Hours }}</li>
<li><strong>Last 7 days:</strong> {{ uniqueVisitorsLast7Days }}</li>
<li><strong>All time:</strong> {{ totalUniqueVisitors }}</li>
</ul>
<p class="analytics-note">Tracked by session ID (includes both anonymous and logged-in visitors)</p>
</div>
<div class="analytics-card">
<h2>Engagement</h2>
<ul class="analytics-stats">
<li><strong>Avg. Visits/Session:</strong> {{ avgVisitsPerSession }}</li>
<li><strong>Bounce Rate:</strong> {{ bounceRate }}%</li>
</ul>
</div>
</div>
<div class="analytics-card">
<h2>Unique Visitors Per Day (Last 7 Days)</h2>
<canvas id="uniqueVisitorsPerDayChart"
data-controller="unique-visitors-per-day-chart"
data-unique-visitors-per-day-chart-labels-value='{{ uniqueVisitorsPerDay|map(stat => stat.day)|json_encode()|e('html_attr') }}'
data-unique-visitors-per-day-chart-counts-value='{{ uniqueVisitorsPerDay|map(stat => stat.count)|json_encode()|e('html_attr') }}'
data-unique-visitors-per-day-chart-labels-value='{{ dailyUniqueVisitorCountsLast7Days|map(stat => stat.day)|json_encode()|e('html_attr') }}'
data-unique-visitors-per-day-chart-counts-value='{{ dailyUniqueVisitorCountsLast7Days|map(stat => stat.count)|json_encode()|e('html_attr') }}'
height="80"></canvas>
<noscript>
<p><em>Enable JavaScript to see the unique visitors per day chart.</em></p>
@ -54,7 +45,7 @@ @@ -54,7 +45,7 @@
</tr>
</thead>
<tbody>
{% for stat in uniqueVisitorsPerDay %}
{% for stat in dailyUniqueVisitorCountsLast7Days %}
<tr>
<td>{{ stat.day }}</td>
<td class="text-right">{{ stat.count }}</td>
@ -64,13 +55,22 @@ @@ -64,13 +55,22 @@
</table>
</div>
<div class="analytics-card">
<h2>Engagement</h2>
<ul class="analytics-stats">
<li><strong>Avg. Visits/Session:</strong> {{ averageVisitsPerSession }}</li>
<li><strong>Bounce Rate:</strong> {{ bounceRate }}%</li>
</ul>
</div>
</div>
<div class="analytics-card">
<h2>Visits Per Day (Last 30 Days)</h2>
{% if visitsPerDay|length > 0 %}
{% if dailyVisitCountsLast30Days|length > 0 %}
<canvas id="visitsPerDayChart"
data-controller="visits-per-day-chart"
data-visits-per-day-chart-labels-value='{{ visitsPerDay|map(stat => stat.day|date('Y-m-d'))|json_encode()|e('html_attr') }}'
data-visits-per-day-chart-counts-value='{{ visitsPerDay|map(stat => stat.count)|json_encode()|e('html_attr') }}'
data-visits-per-day-chart-labels-value='{{ dailyVisitCountsLast30Days|map(stat => stat.day|date('Y-m-d'))|json_encode()|e('html_attr') }}'
data-visits-per-day-chart-counts-value='{{ dailyVisitCountsLast30Days|map(stat => stat.count)|json_encode()|e('html_attr') }}'
height="80"></canvas>
<noscript>
<p><em>Enable JavaScript to see the visits per day chart.</em></p>
@ -84,7 +84,7 @@ @@ -84,7 +84,7 @@
</tr>
</thead>
<tbody>
{% for stat in visitsPerDay %}
{% for stat in dailyVisitCountsLast30Days %}
<tr>
<td>{{ stat.day|date('Y-m-d') }}</td>
<td class="text-right">{{ stat.count }}</td>
@ -99,7 +99,7 @@ @@ -99,7 +99,7 @@
<div class="analytics-card">
<h2>Most Popular Routes (All Time)</h2>
{% if mostPopularRoutes|length > 0 %}
{% if topRoutesAllTime|length > 0 %}
<table class="analytics-table">
<thead>
<tr>
@ -108,7 +108,7 @@ @@ -108,7 +108,7 @@
</tr>
</thead>
<tbody>
{% for stat in mostPopularRoutes %}
{% for stat in topRoutesAllTime %}
<tr>
<td>{{ stat.route }}</td>
<td class="text-right">{{ stat.count }}</td>
@ -123,7 +123,7 @@ @@ -123,7 +123,7 @@
<div class="analytics-card">
<h2>Recent Visits</h2>
{% if recentVisits|length > 0 %}
{% if recentVisitRecords|length > 0 %}
<table class="analytics-table">
<thead>
<tr>
@ -133,7 +133,7 @@ @@ -133,7 +133,7 @@
</tr>
</thead>
<tbody>
{% for visit in recentVisits %}
{% for visit in recentVisitRecords %}
<tr>
<td>{{ visit.route }}</td>
<td><code>{{ visit.sessionId|slice(0, 12) }}...</code></td>
@ -149,8 +149,8 @@ @@ -149,8 +149,8 @@
<div class="analytics-card">
<h2>Visit Count by Route (Last 7 Days)</h2>
{% set filteredVisitStats = visitStats|filter(stat => stat.count >= 5) %}
{% if filteredVisitStats|length > 0 %}
{% set filteredRouteVisitCounts = routeVisitCountsLast7Days|filter(stat => stat.count >= 5) %}
{% if filteredRouteVisitCounts|length > 0 %}
<table class="analytics-table">
<thead>
<tr>
@ -159,7 +159,7 @@ @@ -159,7 +159,7 @@
</tr>
</thead>
<tbody>
{% for stat in filteredVisitStats %}
{% for stat in filteredRouteVisitCounts %}
<tr>
<td>{{ stat.route }}</td>
<td class="text-right">{{ stat.count }}</td>
@ -174,7 +174,7 @@ @@ -174,7 +174,7 @@
<div class="analytics-card">
<h2>Visitor Sessions (Last 7 Days)</h2>
{% if sessionStats|length > 0 %}
{% if visitsBySessionLast7Days|length > 0 %}
<table class="analytics-table">
<thead>
<tr>
@ -185,7 +185,7 @@ @@ -185,7 +185,7 @@
</tr>
</thead>
<tbody>
{% for stat in sessionStats %}
{% for stat in visitsBySessionLast7Days %}
{% if stat.visitCount > 1 %}
<tr>
<td><code>{{ stat.sessionId|slice(0, 12) }}...</code></td>

10
templates/bundles/TwigBundle/Exception/error403.html.twig

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
{% extends 'base.html.twig' %}
{% block title %}Access Denied (403){% endblock %}
{% block body %}
<h1>Access Denied</h1>
<p>You do not have permission to access this page.</p>
<p><a href="{{ path('app_login') }}">Login</a> or <a href="{{ path('home') }}">return to homepage</a>.</p>
{% endblock %}
Loading…
Cancel
Save