main
erb 80 lines 4.89 KB
Raw
1 <% content_for :title, "Dashboard" %>
2
3 <div class="mb-6">
4 <h1 class="text-xl font-semibold text-gray-100">Overview</h1>
5 <p class="text-sm text-gray-500">The business at a glance — growth, catalog, and recurring revenue.</p>
6 </div>
7
8 <%# Growth %>
9 <h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 mb-2">Growth</h2>
10 <div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-6">
11 <%= render "admin/stat", label: "Total users", value: number_with_delimiter(@metrics.total_users),
12 sub: "#{@metrics.admins_count} admin#{'s' unless @metrics.admins_count == 1}" %>
13 <%= render "admin/stat", label: "New · 7 days", value: "+#{number_with_delimiter(@metrics.new_users_7d)}", accent: "text-green-300" %>
14 <%= render "admin/stat", label: "New · 30 days", value: "+#{number_with_delimiter(@metrics.new_users_30d)}", accent: "text-green-300" %>
15 <%= render "admin/stat", label: "Cloud sessions", value: number_with_delimiter(@metrics.cloud_sessions_count) %>
16 </div>
17
18 <%# Revenue %>
19 <h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 mb-2">Recurring revenue</h2>
20 <div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-6">
21 <%= render "admin/stat", label: "Estimated MRR", value: admin_usd(@metrics.estimated_mrr),
22 sub: "active paid plans", accent: "text-brand-300" %>
23 <%= render "admin/stat", label: "Paying · Pro", value: number_with_delimiter(@metrics.paying("pro")) %>
24 <%= render "admin/stat", label: "Paying · Team", value: number_with_delimiter(@metrics.paying("team")) %>
25 <%= render "admin/stat", label: "Trialing", value: number_with_delimiter(@metrics.trialing_count),
26 sub: (@metrics.past_due_count.positive? ? "#{@metrics.past_due_count} past due" : nil),
27 accent: (@metrics.past_due_count.positive? ? "text-amber-300" : "text-gray-100") %>
28 </div>
29
30 <%# Catalog %>
31 <h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 mb-2">Catalog</h2>
32 <div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-8">
33 <%= render "admin/stat", label: "Repositories", value: number_with_delimiter(@metrics.total_repos) %>
34 <%= render "admin/stat", label: "Code repos", value: number_with_delimiter(@metrics.code_repos) %>
35 <%= render "admin/stat", label: "Model repos", value: number_with_delimiter(@metrics.model_repos) %>
36 <%= render "admin/stat", label: "Cloud reqs · month", value: number_with_delimiter(@metrics.cloud_requests_month),
37 sub: "#{number_with_delimiter(@metrics.private_repos)} private repos" %>
38 </div>
39
40 <%# Recent activity %>
41 <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
42 <section>
43 <div class="flex items-center justify-between mb-2">
44 <h2 class="text-sm font-semibold text-gray-200">Newest users</h2>
45 <%= link_to "View all →", admin_users_path, class: "text-xs text-brand-400 hover:text-brand-300" %>
46 </div>
47 <div class="rounded-lg border border-surface-600 divide-y divide-surface-600 overflow-hidden">
48 <% @metrics.recent_users.each do |user| %>
49 <%= link_to admin_user_path(user.username), class: "flex items-center gap-3 px-3 py-2 hover:bg-surface-700 transition-colors" do %>
50 <img src="<%= user.avatar_url_or_default %>" alt="" class="w-7 h-7 rounded-full border border-surface-500">
51 <div class="min-w-0 flex-1">
52 <p class="text-sm text-gray-200 truncate"><%= user.username %><% if user.admin? %> <span class="text-brand-400 text-xs">· admin</span><% end %></p>
53 <p class="text-xs text-gray-500 truncate"><%= user.email %></p>
54 </div>
55 <span class="text-xs text-gray-500 whitespace-nowrap"><%= time_ago_in_words(user.created_at) %> ago</span>
56 <% end %>
57 <% end %>
58 <% if @metrics.recent_users.empty? %><p class="px-3 py-4 text-sm text-gray-500">No users yet.</p><% end %>
59 </div>
60 </section>
61
62 <section>
63 <div class="flex items-center justify-between mb-2">
64 <h2 class="text-sm font-semibold text-gray-200">Newest repositories</h2>
65 <%= link_to "View all →", admin_repositories_path, class: "text-xs text-brand-400 hover:text-brand-300" %>
66 </div>
67 <div class="rounded-lg border border-surface-600 divide-y divide-surface-600 overflow-hidden">
68 <% @metrics.recent_repos.each do |repo| %>
69 <%= link_to admin_repository_path(repo.id), class: "flex items-center gap-3 px-3 py-2 hover:bg-surface-700 transition-colors" do %>
70 <div class="min-w-0 flex-1">
71 <p class="text-sm text-gray-200 truncate"><%= repo.user.username %>/<span class="text-gray-100 font-medium"><%= repo.name %></span></p>
72 <p class="text-xs text-gray-500 truncate"><%= repo.kind %><%= " · private" if repo.is_private %></p>
73 </div>
74 <span class="text-xs text-gray-500 whitespace-nowrap"><%= time_ago_in_words(repo.created_at) %> ago</span>
75 <% end %>
76 <% end %>
77 <% if @metrics.recent_repos.empty? %><p class="px-3 py-4 text-sm text-gray-500">No repositories yet.</p><% end %>
78 </div>
79 </section>
80 </div>