Implemented useAsync in the app structure for cleaner code
This commit is contained in:
@@ -13,26 +13,17 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { MoreVertical, Plus, Rocket } from "lucide-react";
|
||||
import InstallApplicationDialog from "@/components/dialogs/InstallApplication";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getApplications, removeApp } from "@/lib/dockship/application";
|
||||
import RemoveDialog from "@/components/dialogs/Remove";
|
||||
import { Application } from "@/lib/types";
|
||||
import DeployAppDialog from "@/components/dialogs/DeployApp";
|
||||
import { useAsyncInterval } from "@/hooks/use-async";
|
||||
|
||||
export default function ApplicationsPage() {
|
||||
const [applications, setApplications] = useState<Application[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
const apps = await getApplications();
|
||||
setApplications(apps);
|
||||
};
|
||||
|
||||
fetch();
|
||||
|
||||
const iid = setInterval(fetch, 3000);
|
||||
return () => clearInterval(iid);
|
||||
}, []);
|
||||
const { value: applications, setValue: setApplications } = useAsyncInterval(
|
||||
[],
|
||||
getApplications,
|
||||
3000
|
||||
);
|
||||
|
||||
const remove = async (appId: string) => {
|
||||
await removeApp(appId);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getDeployments } from "@/lib/dockship/deploy";
|
||||
import { getApplications } from "@/lib/dockship/application";
|
||||
import { cn } from "@/lib/utils";
|
||||
import DeployAppDialog from "@/components/dialogs/DeployApp";
|
||||
import { useAsync } from "@/hooks/use-async";
|
||||
|
||||
const statusDot = {
|
||||
running: "bg-chart-2",
|
||||
@@ -19,36 +20,13 @@ const statusDot = {
|
||||
} as const;
|
||||
|
||||
export default function DeploymentsPage() {
|
||||
const [applications, setApplications] = useState<Application[]>([]);
|
||||
const [deployments, setDeployments] = useState<Deployment[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { value: applications } = useAsync([], getApplications);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const [apps, deps] = await Promise.all([
|
||||
getApplications(),
|
||||
getDeployments(),
|
||||
]);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
setApplications(apps);
|
||||
setDeployments(deps.filter((d) => d !== null));
|
||||
} catch (err) {
|
||||
console.error("Failed to load deployments:", err);
|
||||
} finally {
|
||||
if (mounted) setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchData();
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, []);
|
||||
const { value: deployments, loading } = useAsync(
|
||||
[],
|
||||
async () => (await getDeployments()).filter((d) => d !== null),
|
||||
[applications]
|
||||
);
|
||||
|
||||
const appNameByContainer = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
|
||||
@@ -16,22 +16,16 @@ import {
|
||||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useAsync } from "@/hooks/use-async";
|
||||
|
||||
export default function EnvironmentPage() {
|
||||
const [selectedApp, setSelectedApp] = useState("");
|
||||
const [showSecrets, setShowSecrets] = useState<Record<string, boolean>>({});
|
||||
const [envGroups, setEnvGroups] = useState<Record<string, Env>>({});
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
const env = await getEnv();
|
||||
setEnvGroups(env);
|
||||
const vars = Object.keys(env);
|
||||
if (vars.length > 0) setSelectedApp(vars[0]);
|
||||
};
|
||||
fetch();
|
||||
}, []);
|
||||
const [showSecrets, setShowSecrets] = useState<Record<string, boolean>>({});
|
||||
const { value: envGroups, setValue: setEnvGroups } = useAsync({}, getEnv);
|
||||
const { value: selectedApp, setValue: setSelectedApp } = useAsync(
|
||||
"",
|
||||
async () => Object.keys(envGroups)[0] || ""
|
||||
);
|
||||
|
||||
const handleSave = async () => {
|
||||
setIsSaving(true);
|
||||
|
||||
+7
-14
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { DashboardLayout } from "@/components/dashboard/DashboardLayout";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -9,25 +9,18 @@ import { Pause, Play, Download, Filter, RefreshCw } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Log } from "@/lib/types";
|
||||
import { getAllContainerLogs } from "@/lib/dockship/docker";
|
||||
import { useAsyncInterval } from "@/hooks/use-async";
|
||||
|
||||
export default function LogsPage() {
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [filter, setFilter] = useState<"all" | "info" | "warn" | "error">(
|
||||
"all"
|
||||
);
|
||||
const [logs, setLogs] = useState<Log[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
if (!isPaused) setLogs(await getAllContainerLogs());
|
||||
};
|
||||
|
||||
const iid = setInterval(fetch, 1000);
|
||||
|
||||
fetch();
|
||||
|
||||
return () => clearInterval(iid);
|
||||
}, []);
|
||||
const { value: logs, setValue: setLogs } = useAsyncInterval(
|
||||
[],
|
||||
async () => (isPaused ? ([] as Log[]) : getAllContainerLogs()),
|
||||
100
|
||||
);
|
||||
|
||||
const filteredLogs = logs.filter((log) =>
|
||||
filter === "all" ? true : log.level === filter
|
||||
|
||||
+47
-45
@@ -13,6 +13,7 @@ import { getLiveNodes, getNodes } from "@/lib/dockship/node";
|
||||
import { average } from "@/lib/format";
|
||||
import { getContainerStats, getDocker } from "@/lib/dockship/docker";
|
||||
import { SimpleStats } from "@/lib/types";
|
||||
import { useAsync, useAsyncInterval } from "@/hooks/use-async";
|
||||
|
||||
const cpuChartConfig = {
|
||||
cpu: { label: "CPU Usage", color: "var(--chart-1)" },
|
||||
@@ -29,73 +30,74 @@ const networkChartConfig = {
|
||||
|
||||
export default function MonitoringPage() {
|
||||
const [data, setData] = useState<
|
||||
{
|
||||
time: string;
|
||||
cpu: number;
|
||||
memory: number;
|
||||
in: number;
|
||||
out: number;
|
||||
}[]
|
||||
{ time: string; cpu: number; memory: number; in: number; out: number }[]
|
||||
>([]);
|
||||
const isMobile = useIsMobile();
|
||||
const [liveData, setLiveData] = useState<{
|
||||
cpuUsage: number;
|
||||
ramUsage: number;
|
||||
}>();
|
||||
const [containerStats, SetContainerStats] = useState<
|
||||
const [containerStats, setContainerStats] = useState<
|
||||
(SimpleStats & { name: string })[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchNodes = async () => {
|
||||
// Live nodes polling every 3 seconds
|
||||
const { value: liveNodes } = useAsyncInterval(
|
||||
{ cpuUsage: 0, ramUsage: 0 },
|
||||
async () => {
|
||||
const nodes = await getLiveNodes();
|
||||
setLiveData({
|
||||
return {
|
||||
cpuUsage: average(nodes, (n) => n.liveData?.cpu.usage || 0),
|
||||
ramUsage: average(nodes, (n) => n.liveData?.memory.usage || 0),
|
||||
});
|
||||
};
|
||||
fetchNodes();
|
||||
const interval = setInterval(fetchNodes, 3000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
};
|
||||
},
|
||||
3000,
|
||||
[]
|
||||
);
|
||||
|
||||
// Update liveData state
|
||||
useEffect(() => {
|
||||
async function fetchMetrics() {
|
||||
setData(await getMetrics());
|
||||
}
|
||||
setLiveData(liveNodes);
|
||||
}, [liveNodes]);
|
||||
|
||||
fetchMetrics();
|
||||
|
||||
const i = setInterval(fetchMetrics, 30000);
|
||||
|
||||
return () => clearInterval(i);
|
||||
}, []);
|
||||
// Metrics polling every 30 seconds
|
||||
const { value: metrics } = useAsyncInterval(
|
||||
[] as typeof data,
|
||||
getMetrics,
|
||||
30000,
|
||||
[]
|
||||
);
|
||||
|
||||
// Update data state
|
||||
useEffect(() => {
|
||||
async function fetchContainerMetrics() {
|
||||
setData(metrics);
|
||||
}, [metrics]);
|
||||
|
||||
// Container stats fetched once
|
||||
const { value: containers } = useAsync(
|
||||
[] as typeof containerStats,
|
||||
async () => {
|
||||
const nodes = await getNodes();
|
||||
const dockerNodes = await getDocker(nodes);
|
||||
|
||||
SetContainerStats(
|
||||
(
|
||||
await Promise.all(
|
||||
dockerNodes.flatMap((node) =>
|
||||
node.containers.map(async (c) => {
|
||||
const stats = await getContainerStats(c.Id);
|
||||
|
||||
if (stats === undefined) return undefined;
|
||||
|
||||
return { ...stats, name: c.Names[0] };
|
||||
})
|
||||
)
|
||||
return (
|
||||
await Promise.all(
|
||||
dockerNodes.flatMap((node) =>
|
||||
node.containers.map(async (c) => {
|
||||
const stats = await getContainerStats(c.Id);
|
||||
if (!stats) return undefined;
|
||||
return { ...stats, name: c.Names[0] };
|
||||
})
|
||||
)
|
||||
).filter((s): s is SimpleStats & { name: string } => s !== undefined)
|
||||
);
|
||||
)
|
||||
).filter((s): s is SimpleStats & { name: string } => s !== undefined);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
console.log(containerStats);
|
||||
}
|
||||
fetchContainerMetrics();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
setContainerStats(containers);
|
||||
}, [containers]);
|
||||
|
||||
return (
|
||||
<DashboardLayout
|
||||
|
||||
+9
-18
@@ -14,6 +14,7 @@ import { setNodes as updateNodes } from "@/lib/dockship/node";
|
||||
import AddNode from "@/components/dialogs/AddNode";
|
||||
import { getDocker } from "@/lib/dockship/docker";
|
||||
import RemoveDialog from "@/components/dialogs/Remove";
|
||||
import { useAsync, useAsyncInterval } from "@/hooks/use-async";
|
||||
|
||||
function DiskUsage({ liveData }: NodeLiveData) {
|
||||
if (!liveData) return null;
|
||||
@@ -33,25 +34,15 @@ function DiskUsage({ liveData }: NodeLiveData) {
|
||||
}
|
||||
|
||||
export default function NodesPage() {
|
||||
const [nodes, setNodes] = useState<(NodeLiveData & Node)[]>([]);
|
||||
const [dockerNodes, setDockerNodes] = useState<Docker[]>([]);
|
||||
const { value: nodes, setValue: setNodes } = useAsyncInterval(
|
||||
[],
|
||||
getLiveNodes,
|
||||
3000
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchNodes = async () => {
|
||||
const nodes = await getLiveNodes();
|
||||
setNodes(nodes);
|
||||
};
|
||||
fetchNodes();
|
||||
const interval = setInterval(fetchNodes, 3000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDocker = async () => {
|
||||
setDockerNodes(await getDocker(nodes));
|
||||
};
|
||||
fetchDocker();
|
||||
}, [nodes]);
|
||||
const { value: dockerNodes } = useAsync([], async () => getDocker(nodes), [
|
||||
nodes,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DashboardLayout title="Nodes" subtitle="Cluster node management">
|
||||
|
||||
+17
-24
@@ -29,36 +29,29 @@ import { getDocker } from "@/lib/dockship/docker";
|
||||
import { getApplications } from "@/lib/dockship/application";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { getDeployments } from "@/lib/dockship/deploy";
|
||||
import { useAsync, useAsyncInterval } from "@/hooks/use-async";
|
||||
|
||||
export default function OverviewPage() {
|
||||
const [nodes, setNodes] = useState<(NodeLiveData & Node)[]>([]);
|
||||
const [dockerNodes, setDockerNodes] = useState<Docker[]>([]);
|
||||
const [apps, setApplications] = useState<Application[]>([]);
|
||||
const [deployments, setDeployments] = useState<Deployment[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchNodes = async () => {
|
||||
const nodes = await getLiveNodes();
|
||||
setNodes(nodes);
|
||||
setDockerNodes(await getDocker(nodes));
|
||||
setApplications(await getApplications());
|
||||
setDeployments((await getDeployments()).filter((d) => d !== null));
|
||||
};
|
||||
fetchNodes();
|
||||
const interval = setInterval(fetchNodes, 3000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
const { value: nodes } = useAsyncInterval([], getLiveNodes, 3000);
|
||||
const { value: dockerNodes } = useAsync([], async () => getDocker(nodes), [
|
||||
nodes,
|
||||
]);
|
||||
const { value: applications } = useAsyncInterval([], getApplications, 3000);
|
||||
const { value: deployments } = useAsyncInterval(
|
||||
[],
|
||||
async () => (await getDeployments()).filter((d) => d !== null),
|
||||
3000
|
||||
);
|
||||
|
||||
const appNameByContainer = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
for (const app of apps) {
|
||||
for (const app of applications) {
|
||||
for (const container of app.deployments) {
|
||||
map.set(container, app.name);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [apps]);
|
||||
}, [applications]);
|
||||
|
||||
return (
|
||||
<DashboardLayout title="Overview" subtitle="System status and metrics">
|
||||
@@ -66,9 +59,9 @@ export default function OverviewPage() {
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<StatCard
|
||||
title="Applications"
|
||||
value={apps.length}
|
||||
value={applications.length}
|
||||
subtitle={`${
|
||||
apps.filter((app) => app.deployments.length > 0).length
|
||||
applications.filter((app) => app.deployments.length > 0).length
|
||||
} Deployed`}
|
||||
icon={<Box className="w-4 h-4" />}
|
||||
/>
|
||||
@@ -118,12 +111,12 @@ export default function OverviewPage() {
|
||||
</div>
|
||||
|
||||
{/* Rows */}
|
||||
{apps.slice(-5).length === 0 ? (
|
||||
{applications.slice(-5).length === 0 ? (
|
||||
<div className="py-6 text-sm text-muted-foreground text-center">
|
||||
No applications yet
|
||||
</div>
|
||||
) : (
|
||||
apps.slice(-5).map((app) => (
|
||||
applications.slice(-5).map((app) => (
|
||||
<div
|
||||
key={app.id}
|
||||
className="
|
||||
|
||||
Reference in New Issue
Block a user