Skip to content

Commit 4826ccb

Browse files
committed
feat: Add telemetry dashboard to admin portal
1 parent 982aa65 commit 4826ccb

3 files changed

Lines changed: 234 additions & 2 deletions

File tree

webapp/src/app/actions.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,51 @@ export async function deleteAppAction(id: string) {
303303
return { success: false, error: error.message };
304304
}
305305
}
306+
307+
// --- FETCH TELEMETRY STATS ---
308+
export async function fetchTelemetryStatsAction() {
309+
try {
310+
const supabase = getSupabaseAdmin();
311+
312+
// Fetch Installs Count & Latest
313+
const { count: installCount, error: installCountError } = await supabase
314+
.from("installs")
315+
.select("*", { count: 'exact', head: true });
316+
317+
const { data: latestInstalls, error: installError } = await supabase
318+
.from("installs")
319+
.select("*")
320+
.order("last_opened", { ascending: false })
321+
.limit(10);
322+
323+
if (installCountError) console.error("Error fetching install count:", installCountError);
324+
if (installError) console.error("Error fetching installs:", installError);
325+
326+
// Fetch Issues Count & Latest
327+
const { count: issueCount, error: issueCountError } = await supabase
328+
.from("error_logs")
329+
.select("*", { count: 'exact', head: true });
330+
331+
const { data: latestIssues, error: issueError } = await supabase
332+
.from("error_logs")
333+
.select("*")
334+
.order("created_at", { ascending: false })
335+
.limit(10);
336+
337+
if (issueCountError) console.error("Error fetching issue count:", issueCountError);
338+
if (issueError) console.error("Error fetching issues:", issueError);
339+
340+
return {
341+
success: true,
342+
data: {
343+
installCount: installCount || 0,
344+
latestInstalls: latestInstalls || [],
345+
issueCount: issueCount || 0,
346+
latestIssues: latestIssues || []
347+
}
348+
};
349+
} catch (error: any) {
350+
console.error("Fetch Telemetry Error:", error);
351+
return { success: false, error: error.message, data: null };
352+
}
353+
}

webapp/src/app/page.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
"use client";
22

33
import { useState } from "react";
4+
import Dashboard from "@/components/Dashboard";
45
import AdminUploadForm from "@/components/UploadForm";
56
import AppList from "@/components/AppList";
6-
import { UploadCloud, ListChecks } from "lucide-react";
7+
import { UploadCloud, ListChecks, Activity } from "lucide-react";
78

89
const tabs = [
10+
{ id: "dashboard", label: "Dashboard", icon: Activity },
911
{ id: "manage", label: "Manage Apps", icon: ListChecks },
1012
{ id: "upload", label: "Upload New", icon: UploadCloud },
1113
] as const;
1214

1315
type TabId = typeof tabs[number]["id"];
1416

1517
export default function Home() {
16-
const [activeTab, setActiveTab] = useState<TabId>("manage");
18+
const [activeTab, setActiveTab] = useState<TabId>("dashboard");
1719

1820
return (
1921
<main className="min-h-screen bg-[#0B1120] flex flex-col items-center p-4 sm:p-8 relative overflow-hidden">
@@ -57,6 +59,7 @@ export default function Home() {
5759

5860
{/* Main Content Area */}
5961
<div className="z-10 w-full">
62+
{activeTab === "dashboard" && <Dashboard />}
6063
{activeTab === "upload" && <AdminUploadForm />}
6164
{activeTab === "manage" && <AppList />}
6265
</div>
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { fetchTelemetryStatsAction } from "@/app/actions";
5+
import { Activity, AlertTriangle, MonitorSmartphone, Calendar, Download } from "lucide-react";
6+
7+
type InstallRecord = {
8+
id: string;
9+
device_id: string;
10+
brand: string;
11+
model: string;
12+
system_version: string;
13+
last_opened: string;
14+
};
15+
16+
type IssueRecord = {
17+
id: string;
18+
device_id: string;
19+
error_message: string;
20+
stack_trace: string;
21+
platform: string;
22+
created_at: string;
23+
};
24+
25+
type TelemetryData = {
26+
installCount: number;
27+
latestInstalls: InstallRecord[];
28+
issueCount: number;
29+
latestIssues: IssueRecord[];
30+
};
31+
32+
export default function Dashboard() {
33+
const [data, setData] = useState<TelemetryData | null>(null);
34+
const [isLoading, setIsLoading] = useState(true);
35+
const [error, setError] = useState<string | null>(null);
36+
37+
useEffect(() => {
38+
async function loadStats() {
39+
try {
40+
const result = await fetchTelemetryStatsAction();
41+
if (result.success && result.data) {
42+
setData(result.data as TelemetryData);
43+
} else {
44+
setError(result.error || "Failed to load telemetry data.");
45+
}
46+
} catch (e: any) {
47+
setError(e.message);
48+
} finally {
49+
setIsLoading(false);
50+
}
51+
}
52+
loadStats();
53+
}, []);
54+
55+
if (isLoading) {
56+
return (
57+
<div className="w-full max-w-4xl mx-auto flex justify-center items-center h-64 bg-slate-900/40 rounded-2xl border border-slate-700/50">
58+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
59+
</div>
60+
);
61+
}
62+
63+
if (error) {
64+
return (
65+
<div className="w-full max-w-4xl mx-auto p-4 bg-red-500/10 border border-red-500/50 rounded-xl text-red-400">
66+
Error loading dashboard: {error}
67+
</div>
68+
);
69+
}
70+
71+
return (
72+
<div className="w-full max-w-5xl mx-auto space-y-6">
73+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
74+
75+
{/* Installs Card */}
76+
<div className="bg-gradient-to-br from-blue-900/40 to-slate-900/60 backdrop-blur-md rounded-2xl p-6 border border-blue-500/20 shadow-xl shadow-blue-900/20 relative overflow-hidden group">
77+
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
78+
<Download className="w-24 h-24 text-blue-400" />
79+
</div>
80+
<div className="relative z-10 flex flex-col h-full justify-between">
81+
<div>
82+
<p className="text-blue-300 font-medium tracking-wide text-sm mb-1 uppercase flex items-center gap-2">
83+
<Activity className="w-4 h-4" /> Total Installs
84+
</p>
85+
<h2 className="text-5xl font-extrabold text-white tracking-tight">{data?.installCount || 0}</h2>
86+
</div>
87+
<div className="mt-4 pt-4 border-t border-blue-500/20">
88+
<p className="text-slate-400 text-xs">Tracking active across all devices</p>
89+
</div>
90+
</div>
91+
</div>
92+
93+
{/* Issues Card */}
94+
<div className="bg-gradient-to-br from-red-900/40 to-slate-900/60 backdrop-blur-md rounded-2xl p-6 border border-red-500/20 shadow-xl shadow-red-900/20 relative overflow-hidden group">
95+
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
96+
<AlertTriangle className="w-24 h-24 text-red-500" />
97+
</div>
98+
<div className="relative z-10 flex flex-col h-full justify-between">
99+
<div>
100+
<p className="text-red-300 font-medium tracking-wide text-sm mb-1 uppercase flex items-center gap-2">
101+
<AlertTriangle className="w-4 h-4" /> Reported Issues
102+
</p>
103+
<h2 className="text-5xl font-extrabold text-white tracking-tight">{data?.issueCount || 0}</h2>
104+
</div>
105+
<div className="mt-4 pt-4 border-t border-red-500/20">
106+
<p className="text-slate-400 text-xs">Crash reports and tracked metrics</p>
107+
</div>
108+
</div>
109+
</div>
110+
</div>
111+
112+
{/* Grid for lists */}
113+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
114+
115+
{/* Recent Installs */}
116+
<div className="bg-slate-900/60 backdrop-blur-md rounded-2xl border border-slate-700/50 overflow-hidden flex flex-col max-h-[500px]">
117+
<div className="p-4 bg-slate-800/50 border-b border-slate-700/50 flex items-center justify-between">
118+
<h3 className="font-semibold text-slate-200 flex items-center gap-2">
119+
<Download className="w-4 h-4 text-blue-400" /> Recent Installs
120+
</h3>
121+
<span className="text-xs px-2 py-1 bg-slate-700/50 text-slate-400 rounded-md">Last 10</span>
122+
</div>
123+
<div className="overflow-y-auto p-4 space-y-3 custom-scrollbar">
124+
{data?.latestInstalls?.length === 0 ? (
125+
<p className="text-slate-500 text-sm text-center py-4">No recent installs found.</p>
126+
) : (
127+
data?.latestInstalls?.map((install) => (
128+
<div key={install.id} className="bg-slate-800/40 rounded-xl p-3 border border-slate-700/30 flex justify-between items-center hover:bg-slate-800/60 transition-colors">
129+
<div className="flex flex-col">
130+
<div className="flex items-center gap-2">
131+
<MonitorSmartphone className="w-4 h-4 text-slate-400" />
132+
<span className="text-sm font-medium text-slate-200">{install.brand} {install.model || 'Unknown Device'}</span>
133+
<span className="text-xs px-1.5 py-0.5 rounded-md bg-blue-500/10 text-blue-400 border border-blue-500/20">OS {install.system_version}</span>
134+
</div>
135+
<span className="text-xs text-slate-500 mt-1 flex items-center gap-1">
136+
<Calendar className="w-3 h-3" /> {new Date(install.last_opened).toLocaleString()} • ID: {install.device_id.substring(0, 8)}...
137+
</span>
138+
</div>
139+
</div>
140+
))
141+
)}
142+
</div>
143+
</div>
144+
145+
{/* Recent Issues */}
146+
<div className="bg-slate-900/60 backdrop-blur-md rounded-2xl border border-slate-700/50 overflow-hidden flex flex-col max-h-[500px]">
147+
<div className="p-4 bg-slate-800/50 border-b border-slate-700/50 flex items-center justify-between">
148+
<h3 className="font-semibold text-slate-200 flex items-center gap-2">
149+
<AlertTriangle className="w-4 h-4 text-red-500" /> Recent Issues
150+
</h3>
151+
<span className="text-xs px-2 py-1 bg-slate-700/50 text-slate-400 rounded-md">Last 10</span>
152+
</div>
153+
<div className="overflow-y-auto p-4 space-y-3 custom-scrollbar">
154+
{data?.latestIssues?.length === 0 ? (
155+
<p className="text-slate-500 text-sm text-center py-4">No issues reported recently.</p>
156+
) : (
157+
data?.latestIssues?.map((issue) => (
158+
<div key={issue.id} className="bg-slate-800/40 rounded-xl p-3 border border-slate-700/30 flex flex-col gap-2 hover:bg-slate-800/60 transition-colors">
159+
<div className="flex justify-between items-start">
160+
<div className="flex items-center gap-2">
161+
<span className="text-xs px-2 py-0.5 rounded-md bg-red-500/10 text-red-400 border border-red-500/20 uppercase font-bold tracking-wider">{issue.error_message || 'Error'}</span>
162+
<span className="text-xs text-slate-500 flex items-center gap-1">
163+
<Calendar className="w-3 h-3" /> {issue.created_at ? new Date(issue.created_at).toLocaleString() : 'N/A'}
164+
</span>
165+
</div>
166+
</div>
167+
<p className="text-sm text-slate-300 bg-slate-900/40 p-2 rounded-lg border border-slate-800 font-mono text-xs overflow-hidden text-ellipsis whitespace-nowrap">{issue.stack_trace}</p>
168+
<div className="flex items-center gap-2 mt-1">
169+
<MonitorSmartphone className="w-3 h-3 text-slate-500" />
170+
<span className="text-xs text-slate-500">{issue.platform} • Device ID: {issue.device_id ? `${issue.device_id.substring(0, 8)}...` : 'N/A'}</span>
171+
</div>
172+
</div>
173+
))
174+
)}
175+
</div>
176+
</div>
177+
178+
</div>
179+
</div>
180+
);
181+
}

0 commit comments

Comments
 (0)