17.3 KB
raw
{% extends 'base.html' %}
{% block extra_js %}
<script id="chart-total-events-data" type="application/json">{{ total_events_graph | tojson | safe }}</script>
<script id="chart-total-events-by-browser-data" type="application/json">{{ total_events_by_browser | tojson | safe }}</script>
<script id="chart-total-events-by-screen-size-data" type="application/json">{{ total_events_by_screen_size | tojson | safe }}</script>
<script id="chart-total-events-by-device-data" type="application/json">{{ total_events_by_device | tojson | safe }}</script>
<script id="chart-total-events-by-platform-data" type="application/json">{{ total_events_by_platform | tojson | safe }}</script>
<script id="map-session-starts-by-country" type="application/json">{{ session_starts_by_country | tojson | safe }}</script>
<script id="map-session-starts-by-country-region" type="application/json">{{ session_starts_by_country_region | tojson | safe }}</script>
<script type="module" src="{{ vite_asset('static_src/properties/index.js') }}"></script>
{% endblock %}
{% block breadcrumbs %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="/properties">Properties</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ page.title }}</li>
</ol>
</nav>
{% endblock %}
{% block main %}
<div class="bg-deep border-bottom border-subtle py-4">
<div class="container">
<div class="row align-items-center g-3">
<div class="col-12 col-lg-6">
<div class="section-label mb-2">property · <span class="text-green">{{ total_live_users }} live</span></div>
<h1 class="mb-1 text-white fw-bolder d-flex align-items-center gap-3" style="letter-spacing: -0.01em; min-width: 0;">
<span class="text-truncate">{{ property.name }}</span>
<span class="status-dot status-dot-lg {% if total_live_users %}is-up{% else %}is-idle{% endif %}" aria-hidden="true"></span>
</h1>
<div class="text-muted small" style="font-family: var(--bs-font-monospace);">{{ property.id }}</div>
</div>
<div class="col-12 col-lg-6 d-flex flex-wrap gap-2 justify-content-lg-end align-items-center d-print-none">
{% if user.is_authenticated %}
<form id="is-public-form" method="POST" action="/properties/{{ property.id }}/public" class="d-inline-flex align-items-center gap-2">
<span class="toggle-label" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Public properties are viewable by anyone with the URL — no login required.">Visibility</span>
<label class="toggle-pill">
<input type="checkbox" name="is_public" id="is-public-switch" {% if property.is_public %}checked{% endif %}>
<span class="toggle-pill-seg toggle-pill-private">Private</span>
<span class="toggle-pill-seg toggle-pill-public">Public</span>
</label>
</form>
<button type="button" class="btn btn-sm btn-outline-light" data-bs-toggle="modal" data-bs-target="#siteTagModal">
Site tag
</button>
<a href="/{{ property.id }}?report" target="_blank" class="btn btn-sm btn-outline-light">Report · pdf</a>
<a href="/{{ property.id }}?report=md" target="_blank" class="btn btn-sm btn-outline-light">Report · md</a>
{% if not property.is_protected %}
<button type="button" class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#delete-modal-{{ property.id }}">
Delete
</button>
<div class="modal fade" id="delete-modal-{{ property.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-white">Confirm delete</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Delete <strong class="text-white">{{ property.name }}</strong>? All collected events will be removed.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">Cancel</button>
<form method="post" action="/properties/{{ property.id }}/delete" class="d-inline">
<button type="submit" class="btn btn-outline-danger">Delete</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
<div class="row mt-3 g-3 align-items-end d-print-none">
{% if filter_url %}
<div class="col-12">
<span class="filter-chip">
<span class="section-label" style="letter-spacing: 0.1em;">filter · url</span>
<span class="filter-chip-label">{{ filter_url }}</span>
<button type="button" class="filter-chip-clear btn-filter-clear" data-filter-key="filter_url" data-filter-value="{{ filter_url }}" aria-label="Clear filter">✕</button>
</span>
</div>
{% endif %}
<div class="col-12 col-lg-8 offset-lg-4">
<form method="GET">
<div class="date-range-form">
<div class="form-floating">
<input type="date" name="date_start" id="date-start" class="form-control" value="{{ date_start | default('') }}" />
<label for="date-start">Date start</label>
</div>
<div class="form-floating">
<input type="date" name="date_end" id="date-end" class="form-control" value="{{ date_end | default('') }}" />
<label for="date-end">Date end</label>
</div>
<div class="form-floating">
<select name="date_range" id="date-range" class="form-select">
<option value="custom">Custom</option>
<option value="7" {% if date_range == 7 %}selected{% endif %}>7 days</option>
<option value="14" {% if date_range == 14 %}selected{% endif %}>14 days</option>
<option value="28" {% if date_range == 28 %}selected{% endif %}>28 days</option>
<option value="90" {% if date_range == 90 %}selected{% endif %}>3 months</option>
<option value="180" {% if date_range == 180 %}selected{% endif %}>6 months</option>
<option value="365" {% if date_range == 365 %}selected{% endif %}>1 year</option>
</select>
<label for="date-range">Date range</label>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="container my-4 position-relative">
<div class="d-flex align-items-center justify-content-between mb-2">
<div class="section-label">metrics · period vs previous</div>
{% if user.is_authenticated and custom_events | length > 0 %}
<div class="dropdown d-print-none">
<button class="btn btn-sm btn-outline-light dropdown-toggle" type="button" id="customCards" data-bs-toggle="dropdown" aria-expanded="false">
Custom cards
</button>
<div class="dropdown-menu dropdown-menu-end p-3" style="width: 300px;" aria-labelledby="customCards">
<form id="custom-card-form" method="POST" action="/properties/{{ property.id }}/cards">
{% for ce in custom_events %}
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="{{ ce.event }}" id="{{ ce.event }}-switch" {% if ce.active %}checked{% endif %}>
<label class="form-check-label small" for="{{ ce.event }}-switch">{{ ce.event }}</label>
</div>
{% endfor %}
</form>
</div>
</div>
{% endif %}
</div>
<div class="row g-3">
{% for c in event_cards %}
<div class="col-6 col-md-4 col-lg-3">
<div class="metric-tile">
<div class="metric-header">
<div class="metric-label text-truncate" {% if c.help_text %}data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{ c.help_text }}"{% endif %}>
{{ c.name }}
</div>
{% if c.percent_change == 0 %}
<span class="metric-delta is-flat" title="No change vs previous period">·</span>
{% elif c.percent_change < 0 %}
<span class="metric-delta is-down">{{ c.percent_change }}%</span>
{% else %}
<span class="metric-delta is-up">+{{ c.percent_change }}%</span>
{% endif %}
</div>
<div class="metric-value">{{ c.value }}</div>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="container my-4">
<div class="row g-3">
<div class="col-12 col-md-8">
<div class="chart-panel mb-3">
<div class="chart-panel-header">
<span class="chart-panel-title">events over time</span>
</div>
<div class="chart-panel-body" style="height: 320px;">
<canvas id="chart-total-events"></canvas>
</div>
</div>
<div class="chart-panel">
<div class="chart-panel-header chart-panel-header-with-action">
<span class="chart-panel-title" id="map-title">sessions · world</span>
<button type="button" id="map-back" class="chart-panel-action" hidden>← back to world</button>
</div>
<div class="chart-panel-body">
<div id="map" style="min-height: 320px; position: relative;"></div>
</div>
</div>
</div>
<div id="doughnut-graphs" class="col-12 col-md-4">
<div class="chart-panel mb-3">
<div class="chart-panel-header"><span class="chart-panel-title">device</span></div>
<div class="chart-panel-body"><canvas id="chart-total-events-by-device"></canvas></div>
</div>
<div class="chart-panel mb-3">
<div class="chart-panel-header"><span class="chart-panel-title">browser</span></div>
<div class="chart-panel-body"><canvas id="chart-total-events-by-browser"></canvas></div>
</div>
<div class="chart-panel mb-3">
<div class="chart-panel-header"><span class="chart-panel-title">platform</span></div>
<div class="chart-panel-body"><canvas id="chart-total-events-by-platform"></canvas></div>
</div>
<div class="chart-panel mb-3">
<div class="chart-panel-header"><span class="chart-panel-title">screen size</span></div>
<div class="chart-panel-body"><canvas id="chart-total-events-by-screen-size"></canvas></div>
</div>
</div>
</div>
</div>
<div class="container my-4">
<div class="section-label mb-2">top lists · ranked</div>
<div id="top-lists" class="row g-3">
{% if total_page_views_by_page_url | length > 0 %}
<div class="col-12 col-sm-6 col-lg-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">top pages · page views</span><span class="rank-list-col">count</span></div>
<div class="rank-list-body">
{% for item in total_page_views_by_page_url %}
<div class="rank-list-row">
<span class="rank-label"><a href="/{{ property.id }}?filter_url={{ item.label | urlencode }}">{{ item.label }}</a></span>
<span class="rank-count">{{ item.count }}</span>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if total_events_by_page_url | length > 0 %}
<div class="col-12 col-sm-6 col-lg-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">top pages · all events</span><span class="rank-list-col">count</span></div>
<div class="rank-list-body">
{% for item in total_events_by_page_url %}
<div class="rank-list-row">
<span class="rank-label"><a href="/{{ property.id }}?filter_url={{ item.label | urlencode }}">{{ item.label }}</a></span>
<span class="rank-count">{{ item.count }}</span>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if total_session_starts_by_referrer | length > 0 %}
<div class="col-12 col-sm-6 col-lg-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">top referrers</span><span class="rank-list-col">sessions</span></div>
<div class="rank-list-body">
{% for item in total_session_starts_by_referrer %}
<div class="rank-list-row"><span class="rank-label">{{ item.label }}</span><span class="rank-count">{{ item.count }}</span></div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if total_events_by_custom_event | length > 0 %}
<div class="col-12 col-sm-6 col-lg-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">top custom events</span><span class="rank-list-col">count</span></div>
<div class="rank-list-body">
{% for item in total_events_by_custom_event %}
<div class="rank-list-row"><span class="rank-label">{{ item.label }}</span><span class="rank-count">{{ item.count }}</span></div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if total_page_views_by_utm_medium | length > 0 %}
<div class="col-12 col-sm-6 col-lg-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">utm medium</span><span class="rank-list-col">views</span></div>
<div class="rank-list-body">
{% for item in total_page_views_by_utm_medium %}
<div class="rank-list-row"><span class="rank-label">{{ item.label }}</span><span class="rank-count">{{ item.count }}</span></div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if total_page_views_by_utm_source | length > 0 %}
<div class="col-12 col-sm-6 col-lg-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">utm source</span><span class="rank-list-col">views</span></div>
<div class="rank-list-body">
{% for item in total_page_views_by_utm_source %}
<div class="rank-list-row"><span class="rank-label">{{ item.label }}</span><span class="rank-count">{{ item.count }}</span></div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if total_page_views_by_utm_campaign | length > 0 %}
<div class="col-12 col-sm-6 col-lg-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">utm campaign</span><span class="rank-list-col">views</span></div>
<div class="rank-list-body">
{% for item in total_page_views_by_utm_campaign %}
<div class="rank-list-row"><span class="rank-label">{{ item.label }}</span><span class="rank-count">{{ item.count }}</span></div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
<div class="container my-4">
<div class="section-label mb-2">bot traffic · excluded from metrics above</div>
{% if bot_traffic.total > 0 %}
<div class="row g-3">
<div class="col-12 col-md-4">
<div class="metric-tile">
<div class="metric-label">Total bot events</div>
<div class="metric-value">{{ bot_traffic.total }}</div>
</div>
</div>
{% if bot_traffic.top_bots | length > 0 %}
<div class="col-12 col-md-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">top bots</span><span class="rank-list-col">events</span></div>
<div class="rank-list-body">
{% for item in bot_traffic.top_bots %}
<div class="rank-list-row"><span class="rank-label">{{ item.label }}</span><span class="rank-count">{{ item.count }}</span></div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if bot_traffic.top_pages | length > 0 %}
<div class="col-12 col-md-4">
<div class="rank-list">
<div class="rank-list-header"><span class="rank-list-title">top pages · bot hits</span><span class="rank-list-col">events</span></div>
<div class="rank-list-body">
{% for item in bot_traffic.top_pages %}
<div class="rank-list-row"><span class="rank-label">{{ item.label }}</span><span class="rank-count">{{ item.count }}</span></div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
{% else %}
<div class="text-muted small">No bot events in this range.</div>
{% endif %}
</div>
<div class="modal fade" id="siteTagModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-white">Collector site tag</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="text-muted small mb-3">Drop this into the <code class="text-amber"><head></code> of your site. The collector ID below is scoped to <strong class="text-white">{{ property.name }}</strong>.</p>
<textarea class="codebox" rows="7" readonly><script>
(function(m,e,t,r,i,c,s){m.collectorQueue = m.collectorQueue || r;
m.collectorServer = c; m.collectorId = s; var script = e.createElement(t);
script.src = c + i; e.head.appendChild(script);
})(window,document,'script',[],'/static/collector.js',
'{{ collector_server }}','{{ property.id }}');
</script></textarea>
</div>
</div>
</div>
</div>
{% endblock %}