repos
/ analytics-rust master

analytics-rust

mirror archived upstream

Single-binary self-hosted website analytics on Rust axum: collector API, dashboards, world map, and PDF reports.

analyticsaxumdockerrustself-hostedsqliteviteweb-analytics

6.5 KB · 141 lines · HTML Raw History
  1{% extends 'base.html' %}
  2
  3{% block breadcrumbs %}
  4<nav aria-label="breadcrumb">
  5  <ol class="breadcrumb mb-0">
  6    <li class="breadcrumb-item"><a href="/">Home</a></li>
  7    <li class="breadcrumb-item active" aria-current="page">{{ page.title }}</li>
  8  </ol>
  9</nav>
 10{% endblock %}
 11
 12{% block main %}
 13<div class="container my-4">
 14  <div class="row align-items-center g-3 mb-4">
 15    <div class="col-md-5">
 16      <div class="section-label mb-1">operator</div>
 17      <h1 class="fw-bolder text-white mb-0" style="letter-spacing: -0.01em;">{{ page.title }}</h1>
 18      <p class="text-muted mb-0 small mt-1">Every site you're collecting events from, event totals, and seven-day activity state.</p>
 19    </div>
 20    <div class="col-md-7">
 21      <div class="dashboard-toolbar">
 22        <form method="get" class="search-form flex-grow-1">
 23          <span class="search-icon">
 24            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
 25              <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
 26            </svg>
 27          </span>
 28          <input type="text" class="form-control search-input" name="q" id="id_search" placeholder="grep properties..." {% if q %}value="{{ q }}"{% endif %} />
 29        </form>
 30        <button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePropertyAdd" aria-expanded="false" aria-controls="collapsePropertyAdd" onclick="window.collectorQueue && collectorQueue.push({event: 'add_property_toggle'})">
 31          + new
 32        </button>
 33      </div>
 34    </div>
 35  </div>
 36
 37  <div class="collapse mb-4" id="collapsePropertyAdd">
 38    <div class="bg-surface border-subtle rounded border p-3">
 39      <div class="section-label mb-2">register property</div>
 40      <p class="text-muted small mb-3">Give it any friendly name. You'll get a collector ID to embed right after you save.</p>
 41      <form method="POST" action="/properties" class="dashboard-toolbar">
 42        <input type="text" name="name" id="id_name" class="form-control flex-grow-1" placeholder="e.g. marketing-site" required>
 43        <button type="submit" class="btn btn-primary">Add →</button>
 44      </form>
 45    </div>
 46  </div>
 47
 48  <div class="row g-3 mb-4">
 49    <div class="col-6 col-md-3">
 50      <div class="metric-tile metric-accent-green">
 51        <div class="metric-label">total properties</div>
 52        <div class="metric-value">{{ totals.properties }}</div>
 53      </div>
 54    </div>
 55    <div class="col-6 col-md-3">
 56      <div class="metric-tile metric-accent-green">
 57        <div class="metric-label">total events</div>
 58        <div class="metric-value">{{ totals.events }}</div>
 59      </div>
 60    </div>
 61    <div class="col-6 col-md-3">
 62      <div class="metric-tile metric-accent-green">
 63        <div class="metric-label">page views</div>
 64        <div class="metric-value">{{ totals.page_views }}</div>
 65      </div>
 66    </div>
 67    <div class="col-6 col-md-3">
 68      <div class="metric-tile metric-accent-amber">
 69        <div class="metric-label">sessions</div>
 70        <div class="metric-value">{{ totals.sessions }}</div>
 71      </div>
 72    </div>
 73  </div>
 74
 75  <div class="section-label mb-2">registered properties</div>
 76  {% for property in properties %}
 77    <div class="property-row {% if not property.is_active %}is-quiet{% endif %}">
 78      <div class="pr-main">
 79        <div class="pr-title">
 80          <span class="status-dot {% if property.is_active %}is-up{% else %}is-idle{% endif %}" aria-hidden="true"></span>
 81          <a href="/{{ property.id }}" class="text-truncate" onclick="window.collectorQueue && collectorQueue.push({event: 'view_property'})">{{ property.name }}</a>
 82        </div>
 83        <div class="pr-id"><span class="pr-id-k">id</span>{{ property.id }}</div>
 84      </div>
 85      <div class="pr-side">
 86        <div class="pr-meta">
 87          <div class="pr-meta-cell">
 88            <span class="pr-meta-k">state</span>
 89            <span class="pr-meta-v {% if property.is_active %}text-green{% else %}text-muted{% endif %}">{% if property.is_active %}live{% else %}quiet{% endif %}</span>
 90          </div>
 91          <div class="pr-meta-cell">
 92            <span class="pr-meta-k">events</span>
 93            <span class="pr-meta-v">{{ property.total_events }}</span>
 94          </div>
 95          <div class="pr-meta-cell">
 96            <span class="pr-meta-k">views</span>
 97            <span class="pr-meta-v">{{ property.total_page_views }}</span>
 98          </div>
 99          <div class="pr-meta-cell">
100            <span class="pr-meta-k">sessions</span>
101            <span class="pr-meta-v">{{ property.total_session_starts }}</span>
102          </div>
103        </div>
104        <div class="pr-actions">
105          {% if not property.is_protected %}
106          <button type="button" class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#delete-modal-{{ property.id }}">
107            Delete
108          </button>
109          <div class="modal fade" id="delete-modal-{{ property.id }}" tabindex="-1" aria-hidden="true">
110            <div class="modal-dialog">
111              <div class="modal-content">
112                <div class="modal-header">
113                  <h5 class="modal-title text-white">Confirm delete</h5>
114                  <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
115                </div>
116                <div class="modal-body">
117                  Are you sure you want to delete <strong class="text-white">{{ property.name }}</strong>? All collected events will be removed.
118                </div>
119                <div class="modal-footer">
120                  <button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">Cancel</button>
121                  <form method="post" action="/properties/{{ property.id }}/delete" class="d-inline">
122                    <button type="submit" class="btn btn-outline-danger">Delete</button>
123                  </form>
124                </div>
125              </div>
126            </div>
127          </div>
128          {% endif %}
129          <a href="/{{ property.id }}" class="btn btn-sm btn-outline-light">View →</a>
130        </div>
131      </div>
132    </div>
133  {% else %}
134    <div class="text-center py-5 text-muted">
135      <div class="section-label mb-3" style="justify-content:center;">no properties yet</div>
136      <p class="mb-0 small">Click <strong>+ new</strong> above to register your first site.</p>
137    </div>
138  {% endfor %}
139</div>
140{% endblock %}