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