Skip to content

Commit 458cd78

Browse files
Add common dashboard page (#88)
1 parent 41306ac commit 458cd78

4 files changed

Lines changed: 735 additions & 13 deletions

File tree

frontend/src/App.tsx

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { useState, useEffect, useCallback, useRef } from "react";
1+
import { useState, useEffect, useCallback } from "react";
22
import Sidebar from "./components/Sidebar";
33
import Toolbar from "./components/Toolbar";
44
import EmptyState from "./components/EmptyState";
55
import SkeletonTableLoader from "./components/SkeletonTableLoader";
66
import OverviewView from "./components/views/OverviewView";
7+
import CommonDashboardView from "./components/views/CommonDashboardView";
78
import TableView from "./components/views/TableView";
89
import DocumentsView from "./components/views/DocumentsView";
910
import JsonView from "./components/views/JsonView";
@@ -12,7 +13,7 @@ import QueryWorkbench from "./components/views/QueryWorkbench";
1213
import SchemaView from "./components/views/SchemaView";
1314

1415
export type ViewMode = "table" | "documents" | "json" | "inspector";
15-
export type AppMode = "overview" | "table" | "query" | "schema";
16+
export type AppMode = "common" | "overview" | "table" | "query" | "schema";
1617

1718
export interface DriverCapabilities {
1819
rawQuery: boolean;
@@ -198,6 +199,28 @@ export default function App() {
198199
}
199200
}, []);
200201

202+
const loadCommonDashboard = useCallback(async () => {
203+
setAppMode("common");
204+
setCurrentTable("");
205+
setSearch("");
206+
setLoading(true);
207+
setError("");
208+
try {
209+
const res = await fetch("/api/overview");
210+
const payload = await res.json();
211+
if (!res.ok)
212+
throw new Error(payload.error || "Failed to load common dashboard.");
213+
setOverview(payload);
214+
showStatus("Fleet dashboard ready");
215+
} catch (err: unknown) {
216+
const msg = err instanceof Error ? err.message : "Unknown error";
217+
showStatus(msg, true);
218+
setError(msg);
219+
} finally {
220+
setLoading(false);
221+
}
222+
}, []);
223+
201224
const switchDatabase = async (dbId: string) => {
202225
setActiveDbId(dbId);
203226
setLoading(true);
@@ -215,6 +238,18 @@ export default function App() {
215238
}
216239
};
217240

241+
const openDatabaseOverview = useCallback(
242+
async (dbId: string) => {
243+
await switchDatabase(dbId);
244+
setAppMode("overview");
245+
setCurrentTable("");
246+
setSearch("");
247+
setError("");
248+
},
249+
// eslint-disable-next-line react-hooks/exhaustive-deps
250+
[],
251+
);
252+
218253
const loadTable = useCallback(
219254
async (
220255
name: string,
@@ -274,27 +309,55 @@ export default function App() {
274309
[activeDbId, pageSize],
275310
);
276311

277-
const openQueryWorkspace = useCallback(() => {
312+
const openQueryWorkspace = useCallback(async (targetDbId?: string) => {
313+
if (targetDbId && targetDbId !== activeDbId) {
314+
setActiveDbId(targetDbId);
315+
setLoading(true);
316+
try {
317+
await loadDatabaseMetadata(targetDbId);
318+
} catch (err: unknown) {
319+
const msg = err instanceof Error ? err.message : "Unknown error";
320+
showStatus(msg, true);
321+
setError(msg);
322+
setLoading(false);
323+
return;
324+
}
325+
}
278326
setAppMode("query");
279327
setCurrentTable("");
280328
setSearch("");
281329
setError("");
282330
setLoading(false);
283331
showStatus("Query workspace ready");
284-
}, []);
332+
}, [activeDbId]);
285333

286-
const openSchemaVisualizer = useCallback(() => {
334+
const openSchemaVisualizer = useCallback(async (targetDbId?: string) => {
335+
if (targetDbId && targetDbId !== activeDbId) {
336+
setActiveDbId(targetDbId);
337+
setLoading(true);
338+
try {
339+
await loadDatabaseMetadata(targetDbId);
340+
} catch (err: unknown) {
341+
const msg = err instanceof Error ? err.message : "Unknown error";
342+
showStatus(msg, true);
343+
setError(msg);
344+
setLoading(false);
345+
return;
346+
}
347+
}
287348
setAppMode("schema");
288349
setCurrentTable("");
289350
setSearch("");
290351
setError("");
291352
setLoading(false);
292353
showStatus("Schema visualizer ready");
293-
}, []);
354+
}, [activeDbId]);
294355

295356
const handleReload = () => {
296357
setReloadKey((k) => k + 1);
297-
if (appMode === "overview") {
358+
if (appMode === "common") {
359+
loadCommonDashboard();
360+
} else if (appMode === "overview") {
298361
loadOverview();
299362
} else if (currentTable) {
300363
loadTable(currentTable, activeDbId, sortBy, sortOrder, filters, 0);
@@ -329,9 +392,11 @@ export default function App() {
329392
<EmptyState>
330393
<div className="loading-pulse" />
331394
<p>
332-
{appMode === "overview"
333-
? "Loading overview..."
334-
: "Fetching records..."}
395+
{appMode === "common"
396+
? "Loading common dashboard..."
397+
: appMode === "overview"
398+
? "Loading overview..."
399+
: "Fetching records..."}
335400
</p>
336401
</EmptyState>
337402
);
@@ -347,6 +412,19 @@ export default function App() {
347412
);
348413
}
349414

415+
if (appMode === "common" && overview) {
416+
return (
417+
<CommonDashboardView
418+
overview={overview}
419+
activeDbId={activeDbId}
420+
onDatabaseOverview={openDatabaseOverview}
421+
onQueryClick={openQueryWorkspace}
422+
onSchemaClick={openSchemaVisualizer}
423+
onTableClick={loadTable}
424+
/>
425+
);
426+
}
427+
350428
if (appMode === "overview" && overview) {
351429
const activeDbData =
352430
overview.databases.find((db) => db.id === activeDbId) ||
@@ -457,6 +535,7 @@ export default function App() {
457535
activeTable={currentTable}
458536
appMode={appMode}
459537
capabilities={capabilities}
538+
onCommonDashboardClick={loadCommonDashboard}
460539
onOverviewClick={loadOverview}
461540
onTableClick={loadTable}
462541
onQueryClick={openQueryWorkspace}
@@ -466,7 +545,9 @@ export default function App() {
466545
<main className="main-area">
467546
<Toolbar
468547
title={
469-
appMode === "overview"
548+
appMode === "common"
549+
? "Common Dashboard"
550+
: appMode === "overview"
470551
? "Overview"
471552
: appMode === "query"
472553
? "Query Console"
@@ -475,8 +556,10 @@ export default function App() {
475556
: currentTable || "Select a Table"
476557
}
477558
dbType={
478-
appMode === "overview" && (overview?.databases?.length ?? 0) > 1
479-
? "Overview"
559+
appMode === "common"
560+
? "Fleet"
561+
: appMode === "overview" && (overview?.databases?.length ?? 0) > 1
562+
? "Overview"
480563
: dbType
481564
}
482565
theme={theme}

frontend/src/components/Sidebar.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface SidebarProps {
1818
activeTable: string;
1919
appMode: AppMode;
2020
capabilities: DriverCapabilities;
21+
onCommonDashboardClick: () => void;
2122
onOverviewClick: () => void;
2223
onQueryClick: () => void;
2324
onSchemaClick: () => void;
@@ -57,6 +58,22 @@ const GridIcon = () => (
5758
</svg>
5859
);
5960

61+
const DashboardIcon = () => (
62+
<svg
63+
viewBox="0 0 24 24"
64+
fill="none"
65+
stroke="currentColor"
66+
strokeWidth="2"
67+
strokeLinecap="round"
68+
strokeLinejoin="round"
69+
>
70+
<rect x="3" y="3" width="7" height="8" />
71+
<rect x="14" y="3" width="7" height="5" />
72+
<rect x="14" y="12" width="7" height="9" />
73+
<rect x="3" y="15" width="7" height="6" />
74+
</svg>
75+
);
76+
6077
const QueryIcon = () => (
6178
<svg
6279
viewBox="0 0 24 24"
@@ -115,6 +132,7 @@ export default function Sidebar({
115132
activeTable,
116133
appMode,
117134
capabilities,
135+
onCommonDashboardClick,
118136
onOverviewClick,
119137
onQueryClick,
120138
onSchemaClick,
@@ -159,6 +177,15 @@ export default function Sidebar({
159177
/>
160178
</div>
161179

180+
<button
181+
className={`overview-btn common-dashboard-btn${appMode === "common" ? " active" : ""}`}
182+
onClick={onCommonDashboardClick}
183+
type="button"
184+
>
185+
<DashboardIcon />
186+
<span>Common Dashboard</span>
187+
</button>
188+
162189
<div className="db-selector-wrapper">
163190
<div className="section-label">Active Connection</div>
164191
<div className="db-connection-list">

0 commit comments

Comments
 (0)