feat: Connect dashboard to backend and update tab system to be similar to a browser (neither are fully finished)

This commit is contained in:
LukeGus
2025-10-18 02:54:29 -05:00
parent 3901bc9899
commit a44e2be8a4
16 changed files with 1151 additions and 186 deletions

View File

@@ -3,9 +3,23 @@ import { Auth } from "@/ui/Desktop/Authentication/Auth.tsx";
import { UpdateLog } from "@/ui/Desktop/Apps/Dashboard/Apps/UpdateLog.tsx";
import { AlertManager } from "@/ui/Desktop/Apps/Dashboard/Apps/Alerts/AlertManager.tsx";
import { Button } from "@/components/ui/button.tsx";
import { getUserInfo, getDatabaseHealth, getCookie } from "@/ui/main-axios.ts";
import {
getUserInfo,
getDatabaseHealth,
getCookie,
getUptime,
getVersionInfo,
getSSHHosts,
getTunnelStatuses,
getCredentials,
getRecentActivity,
resetRecentActivity,
getServerMetricsById,
type RecentActivityItem,
} from "@/ui/main-axios.ts";
import { useSidebar } from "@/components/ui/sidebar.tsx";
import { Separator } from "@/components/ui/separator.tsx";
import { useTabs } from "@/ui/Desktop/Navigation/Tabs/TabContext.tsx";
import {
ChartLine,
Clock,
@@ -39,13 +53,33 @@ export function Dashboard({
authLoading,
onAuthSuccess,
isTopbarOpen,
onSelectView,
}: DashboardProps): React.ReactElement {
const [loggedIn, setLoggedIn] = useState(isAuthenticated);
const [, setIsAdmin] = useState(false);
const [isAdmin, setIsAdmin] = useState(false);
const [, setUsername] = useState<string | null>(null);
const [userId, setUserId] = useState<string | null>(null);
const [dbError, setDbError] = useState<string | null>(null);
// Dashboard data state
const [uptime, setUptime] = useState<string>("0d 0h 0m");
const [versionStatus, setVersionStatus] = useState<
"up_to_date" | "requires_update"
>("up_to_date");
const [versionText, setVersionText] = useState<string>("v1.8.0");
const [dbHealth, setDbHealth] = useState<"healthy" | "error">("healthy");
const [totalServers, setTotalServers] = useState<number>(0);
const [totalTunnels, setTotalTunnels] = useState<number>(0);
const [totalCredentials, setTotalCredentials] = useState<number>(0);
const [recentActivity, setRecentActivity] = useState<RecentActivityItem[]>(
[],
);
const [serverStats, setServerStats] = useState<
Array<{ id: number; name: string; cpu: number | null; ram: number | null }>
>([]);
const { addTab } = useTabs();
let sidebarState: "expanded" | "collapsed" = "expanded";
try {
const sidebar = useSidebar();
@@ -99,6 +133,110 @@ export function Dashboard({
}
}, [isAuthenticated]);
// Fetch dashboard data
useEffect(() => {
if (!loggedIn) return;
const fetchDashboardData = async () => {
try {
// Fetch uptime
const uptimeInfo = await getUptime();
setUptime(uptimeInfo.formatted);
// Fetch version info
const versionInfo = await getVersionInfo();
setVersionText(`v${versionInfo.localVersion}`);
setVersionStatus(versionInfo.status || "up_to_date");
// Fetch database health
try {
await getDatabaseHealth();
setDbHealth("healthy");
} catch {
setDbHealth("error");
}
// Fetch total counts
const hosts = await getSSHHosts();
setTotalServers(hosts.length);
const tunnels = await getTunnelStatuses();
setTotalTunnels(Object.keys(tunnels).length);
const credentials = await getCredentials();
setTotalCredentials(credentials.length);
// Fetch recent activity
const activity = await getRecentActivity(10);
setRecentActivity(activity);
// Fetch server stats for first 5 servers
const serversWithStats = await Promise.all(
hosts.slice(0, 5).map(async (host: { id: number; name: string }) => {
try {
const metrics = await getServerMetricsById(host.id);
return {
id: host.id,
name: host.name || `Host ${host.id}`,
cpu: metrics.cpu.percent,
ram: metrics.memory.percent,
};
} catch {
return {
id: host.id,
name: host.name || `Host ${host.id}`,
cpu: null,
ram: null,
};
}
}),
);
setServerStats(serversWithStats);
} catch (error) {
console.error("Failed to fetch dashboard data:", error);
}
};
fetchDashboardData();
// Refresh every 30 seconds
const interval = setInterval(fetchDashboardData, 30000);
return () => clearInterval(interval);
}, [loggedIn]);
// Handler for resetting recent activity
const handleResetActivity = async () => {
try {
await resetRecentActivity();
setRecentActivity([]);
} catch (error) {
console.error("Failed to reset activity:", error);
}
};
// Handler for opening a recent activity item
const handleActivityClick = (item: RecentActivityItem) => {
// Find the host and open appropriate tab
getSSHHosts().then((hosts) => {
const host = hosts.find((h: { id: number }) => h.id === item.hostId);
if (!host) return;
if (item.type === "terminal") {
addTab({
type: "terminal",
title: item.hostName,
hostConfig: host,
});
} else if (item.type === "file_manager") {
addTab({
type: "file_manager",
title: item.hostName,
hostConfig: host,
});
}
});
};
return (
<>
{!loggedIn ? (
@@ -201,14 +339,16 @@ export function Dashboard({
<div className="flex flex-row items-center">
<p className="leading-none text-muted-foreground">
v1.8.0
{versionText}
</p>
<Button
variant="outline"
size="sm"
className="ml-2 text-sm border-1 text-green-400 border-dark-border"
className={`ml-2 text-sm border-1 border-dark-border ${versionStatus === "up_to_date" ? "text-green-400" : "text-yellow-400"}`}
>
Up to Date
{versionStatus === "up_to_date"
? "Up to Date"
: "Update Available"}
</Button>
<UpdateLog loggedIn={loggedIn} />
</div>
@@ -226,7 +366,7 @@ export function Dashboard({
<div className="flex flex-row items-center">
<p className="leading-none text-muted-foreground">
0d 0h 7m
{uptime}
</p>
</div>
</div>
@@ -242,38 +382,55 @@ export function Dashboard({
</div>
<div className="flex flex-row items-center">
<p className="leading-none text-muted-foreground">
healthy
<p
className={`leading-none ${dbHealth === "healthy" ? "text-green-400" : "text-red-400"}`}
>
{dbHealth}
</p>
</div>
</div>
</div>
<div className="flex flex-col grid grid-cols-2 gap-2 mt-2">
<div className="flex flex-row items-center bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3">
<Server
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<p className="m-0 leading-none">Total Servers</p>
<div className="flex flex-row items-center justify-between bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3">
<div className="flex flex-row items-center">
<Server
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<p className="m-0 leading-none">Total Servers</p>
</div>
<p className="m-0 leading-none text-muted-foreground font-semibold">
{totalServers}
</p>
</div>
<div className="flex flex-row items-center bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3">
<Network
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<p className="m-0 leading-none">Total Tunnels</p>
<div className="flex flex-row items-center justify-between bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3">
<div className="flex flex-row items-center">
<Network
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<p className="m-0 leading-none">Total Tunnels</p>
</div>
<p className="m-0 leading-none text-muted-foreground font-semibold">
{totalTunnels}
</p>
</div>
</div>
<div className="flex flex-col grid grid-cols-2 gap-2 mt-2">
<div className="flex flex-row items-center bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3">
<Key
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<p className="m-0 leading-none">Total Credentials</p>
<div className="flex flex-row items-center justify-between bg-dark-bg w-full h-auto mt-3 border-2 border-dark-border rounded-md px-3 py-3">
<div className="flex flex-row items-center">
<Key
size={16}
color="#FFFFFF"
className="mr-3 shrink-0"
/>
<p className="m-0 leading-none">Total Credentials</p>
</div>
<p className="m-0 leading-none text-muted-foreground font-semibold">
{totalCredentials}
</p>
</div>
</div>
</div>
@@ -289,18 +446,31 @@ export function Dashboard({
variant="outline"
size="sm"
className="border-2 !border-dark-border h-7"
onClick={handleResetActivity}
>
Reset
</Button>
</div>
<div className="grid gap-4 grid-cols-[repeat(auto-fit,minmax(200px,1fr))] auto-rows-min overflow-y-auto overflow-x-hidden">
<Button
variant="outline"
className="border-2 !border-dark-border bg-dark-bg"
>
<Server size={20} className="shrink-0" />
<p className="truncate ml-2 font-semibold">Server #1</p>
</Button>
{recentActivity.length === 0 ? (
<p className="text-muted-foreground text-sm">
No recent activity
</p>
) : (
recentActivity.map((item) => (
<Button
key={item.id}
variant="outline"
className="border-2 !border-dark-border bg-dark-bg"
onClick={() => handleActivityClick(item)}
>
<Server size={20} className="shrink-0" />
<p className="truncate ml-2 font-semibold">
{item.hostName}
</p>
</Button>
))
)}
</div>
</div>
</div>
@@ -316,6 +486,7 @@ export function Dashboard({
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3"
onClick={() => onSelectView("host-manager-add")}
>
<Server
className="shrink-0"
@@ -328,6 +499,7 @@ export function Dashboard({
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3"
onClick={() => onSelectView("host-manager-credentials")}
>
<Key
className="shrink-0"
@@ -337,21 +509,25 @@ export function Dashboard({
Add Credential
</span>
</Button>
{isAdmin && (
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3"
onClick={() => onSelectView("admin-settings")}
>
<Settings
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span className="font-semibold text-sm mt-2">
Admin Settings
</span>
</Button>
)}
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3"
>
<Settings
className="shrink-0"
style={{ width: "40px", height: "40px" }}
/>
<span className="font-semibold text-sm mt-2">
Admin Settings
</span>
</Button>
<Button
variant="outline"
className="border-2 !border-dark-border flex flex-col items-center justify-center h-auto p-3"
onClick={() => onSelectView("user-profile")}
>
<User
className="shrink-0"
@@ -371,23 +547,42 @@ export function Dashboard({
Server Stats
</p>
<div className="grid gap-4 grid-cols-[repeat(auto-fit,minmax(200px,1fr))] auto-rows-min overflow-y-auto overflow-x-hidden">
<Button
variant="outline"
className="border-2 !border-dark-border bg-dark-bg h-auto p-3"
>
<div className="flex flex-col w-full">
<div className="flex flex-row items-center mb-2">
<Server size={20} className="shrink-0" />
<p className="truncate ml-2 font-semibold">
Server #1
</p>
</div>
<div className="flex flex-row justify-between text-xs text-muted-foreground">
<span>CPU: 45%</span>
<span>RAM: 62%</span>
</div>
</div>
</Button>
{serverStats.length === 0 ? (
<p className="text-muted-foreground text-sm">
No server data available
</p>
) : (
serverStats.map((server) => (
<Button
key={server.id}
variant="outline"
className="border-2 !border-dark-border bg-dark-bg h-auto p-3"
>
<div className="flex flex-col w-full">
<div className="flex flex-row items-center mb-2">
<Server size={20} className="shrink-0" />
<p className="truncate ml-2 font-semibold">
{server.name}
</p>
</div>
<div className="flex flex-row justify-between text-xs text-muted-foreground">
<span>
CPU:{" "}
{server.cpu !== null
? `${server.cpu}%`
: "N/A"}
</span>
<span>
RAM:{" "}
{server.ram !== null
? `${server.ram}%`
: "N/A"}
</span>
</div>
</div>
</Button>
))
)}
</div>
</div>
</div>