229 lines
11 KiB
HTML
229 lines
11 KiB
HTML
{{define "content"}}
|
|
<div class="mb-6">
|
|
<a href="/dashboard" class="inline-flex items-center text-sm text-gray-500 hover:text-gray-700">
|
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
|
</svg>
|
|
Back to Dashboard
|
|
</a>
|
|
</div>
|
|
|
|
<!-- App Header -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="w-16 h-16 bg-gray-100 rounded-xl flex items-center justify-center">
|
|
<svg class="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-900">{{.App.Name}}</h1>
|
|
<p class="text-gray-500">{{.App.PackageID}}</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<select id="days-select" class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-indigo-500 focus:border-indigo-500"
|
|
hx-get="/apps/{{.App.ID}}/analytics"
|
|
hx-trigger="change"
|
|
hx-target="#analytics-content"
|
|
hx-select="#analytics-content"
|
|
hx-include="this">
|
|
<option value="7" {{if eq .Days 7}}selected{{end}}>Last 7 days</option>
|
|
<option value="30" {{if eq .Days 30}}selected{{end}}>Last 30 days</option>
|
|
<option value="90" {{if eq .Days 90}}selected{{end}}>Last 90 days</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="border-b border-gray-200 mb-6">
|
|
<nav class="-mb-px flex space-x-8">
|
|
<a href="/apps/{{.App.ID}}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
|
|
Overview
|
|
</a>
|
|
<a href="/apps/{{.App.ID}}/versions" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
|
|
Versions
|
|
</a>
|
|
<a href="/apps/{{.App.ID}}/analytics" class="border-indigo-500 text-indigo-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
|
|
Analytics
|
|
</a>
|
|
<a href="/apps/{{.App.ID}}/settings" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
|
|
Settings
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Analytics Content -->
|
|
<div id="analytics-content">
|
|
<!-- Overview Stats -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<p class="text-sm font-medium text-gray-500">Daily Active Users</p>
|
|
<p class="mt-1 text-3xl font-semibold text-gray-900">{{.Overview.DAU}}</p>
|
|
</div>
|
|
<div class="{{if ge .Overview.DAUChange 0.0}}text-green-500{{else}}text-red-500{{end}}">
|
|
{{if ge .Overview.DAUChange 0.0}}
|
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
|
|
</svg>
|
|
{{else}}
|
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6"/>
|
|
</svg>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
<p class="mt-2 text-sm {{if ge .Overview.DAUChange 0.0}}text-green-600{{else}}text-red-600{{end}}">
|
|
{{if ge .Overview.DAUChange 0.0}}+{{end}}{{printf "%.1f" .Overview.DAUChange}}% from previous period
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<p class="text-sm font-medium text-gray-500">Total Sessions</p>
|
|
<p class="mt-1 text-3xl font-semibold text-gray-900">{{.Overview.TotalSessions}}</p>
|
|
</div>
|
|
<div class="text-indigo-500">
|
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<p class="text-sm font-medium text-gray-500">Crash-Free Rate</p>
|
|
<p class="mt-1 text-3xl font-semibold text-gray-900">{{printf "%.1f" .Overview.CrashFreeRate}}%</p>
|
|
</div>
|
|
<div class="{{if ge .Overview.CrashFreeRate 99.0}}text-green-500{{else if ge .Overview.CrashFreeRate 95.0}}text-yellow-500{{else}}text-red-500{{end}}">
|
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<p class="text-sm font-medium text-gray-500">Total Crashes</p>
|
|
<p class="mt-1 text-3xl font-semibold text-gray-900">{{.Overview.TotalCrashes}}</p>
|
|
</div>
|
|
<div class="{{if eq .Overview.TotalCrashes 0}}text-green-500{{else}}text-red-500{{end}}">
|
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
{{if gt .Overview.TotalCrashes 0}}
|
|
<a href="/apps/{{.App.ID}}/crashes" class="mt-2 text-sm text-indigo-600 hover:text-indigo-700">
|
|
View crash reports →
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
<!-- DAU Chart -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Daily Active Users</h3>
|
|
<div id="dau-chart" class="h-64"
|
|
hx-get="/apps/{{.App.ID}}/partials/chart-dau?days={{.Days}}"
|
|
hx-trigger="load">
|
|
<div class="flex items-center justify-center h-full text-gray-400">
|
|
<svg class="animate-spin h-8 w-8" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sessions Chart -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Sessions</h3>
|
|
<div id="sessions-chart" class="h-64"
|
|
hx-get="/apps/{{.App.ID}}/partials/chart-sessions?days={{.Days}}"
|
|
hx-trigger="load">
|
|
<div class="flex items-center justify-center h-full text-gray-400">
|
|
<svg class="animate-spin h-8 w-8" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Event Types -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Event Distribution</h3>
|
|
{{if .EventStats}}
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead>
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Unique Devices</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
{{range .EventStats}}
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{.EventType}}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-right">{{.Count}}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-right">{{.UniqueDevices}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{else}}
|
|
<p class="text-gray-500 text-center py-8">No events recorded yet.</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<!-- Recent Crashes -->
|
|
{{if .Crashes}}
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-900">Recent Crashes</h3>
|
|
<a href="/apps/{{.App.ID}}/crashes" class="text-sm text-indigo-600 hover:text-indigo-700">
|
|
View all →
|
|
</a>
|
|
</div>
|
|
<div class="space-y-4">
|
|
{{range .Crashes}}
|
|
<div class="flex items-start p-4 bg-red-50 border border-red-100 rounded-lg">
|
|
<div class="flex-shrink-0">
|
|
<svg class="w-5 h-5 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3 flex-1">
|
|
<p class="text-sm font-medium text-red-800">{{.CrashType}}: {{.Message}}</p>
|
|
<div class="mt-1 flex items-center space-x-4 text-xs text-red-600">
|
|
<span>{{.OccurrenceCount}} occurrences</span>
|
|
<span>Last seen: {{.LastSeen}}</span>
|
|
</div>
|
|
</div>
|
|
<span class="ml-4 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium {{if eq .Status "open"}}bg-red-100 text-red-800{{else if eq .Status "resolved"}}bg-green-100 text-green-800{{else}}bg-gray-100 text-gray-800{{end}}">
|
|
{{.Status}}
|
|
</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|