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. 94
      templates/admin/analytics.html.twig
  5. 10
      templates/bundles/TwigBundle/Exception/error403.html.twig

1
config/packages/security.yaml

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

61
src/Controller/Administration/VisitorAnalyticsController.php

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

32
src/Security/CustomAccessDeniedHandler.php

@ -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);
}
}

94
templates/admin/analytics.html.twig

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

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

@ -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