Caching
This commit is contained in:
@@ -20,9 +20,10 @@ import { useAsyncInterval } from "@/hooks/use-async";
|
|||||||
|
|
||||||
export default function ApplicationsPage() {
|
export default function ApplicationsPage() {
|
||||||
const { value: apps, setValue: setApps } = useAsyncInterval(
|
const { value: apps, setValue: setApps } = useAsyncInterval(
|
||||||
|
"apps",
|
||||||
[],
|
[],
|
||||||
getApps,
|
getApps,
|
||||||
3000
|
3000,
|
||||||
);
|
);
|
||||||
|
|
||||||
const remove = async (appId: string) => {
|
const remove = async (appId: string) => {
|
||||||
|
|||||||
@@ -25,9 +25,14 @@ const statusDot = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default function DeploymentsPage() {
|
export default function DeploymentsPage() {
|
||||||
const { value: apps } = useAsyncInterval([], getApps, 5000);
|
const { value: apps } = useAsyncInterval("apps", [], getApps, 5000);
|
||||||
|
|
||||||
const { value: deployments, loading } = useAsync([], getDeployments, [apps]);
|
const { value: deployments, loading } = useAsync(
|
||||||
|
"deployments",
|
||||||
|
[],
|
||||||
|
getDeployments,
|
||||||
|
[apps],
|
||||||
|
);
|
||||||
|
|
||||||
const appNameByContainer = useMemo(() => {
|
const appNameByContainer = useMemo(() => {
|
||||||
const map = new Map<string, string>();
|
const map = new Map<string, string>();
|
||||||
|
|||||||
@@ -21,11 +21,16 @@ import { useAsync } from "@/hooks/use-async";
|
|||||||
export default function EnvironmentPage() {
|
export default function EnvironmentPage() {
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [showSecrets, setShowSecrets] = useState<Record<string, boolean>>({});
|
const [showSecrets, setShowSecrets] = useState<Record<string, boolean>>({});
|
||||||
const { value: envGroups, setValue: setEnvGroups } = useAsync({}, getEnv);
|
const { value: envGroups, setValue: setEnvGroups } = useAsync(
|
||||||
|
"env",
|
||||||
|
{},
|
||||||
|
getEnv,
|
||||||
|
);
|
||||||
const { value: selectedApp, setValue: setSelectedApp } = useAsync(
|
const { value: selectedApp, setValue: setSelectedApp } = useAsync(
|
||||||
|
"selectedApp",
|
||||||
"",
|
"",
|
||||||
async () => Object.keys(envGroups)[0] || "",
|
async () => Object.keys(envGroups)[0] || "",
|
||||||
[envGroups]
|
[envGroups],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
|
|||||||
@@ -31,10 +31,17 @@ export default function MonitoringPage() {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
// Metrics polling every 30 seconds
|
// Metrics polling every 30 seconds
|
||||||
const { value: metrics } = useAsyncInterval([], getMetrics, 30000, []);
|
const { value: metrics } = useAsyncInterval(
|
||||||
|
"metrics",
|
||||||
|
[],
|
||||||
|
getMetrics,
|
||||||
|
30000,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
// Live nodes polling every 3 seconds
|
// Live nodes polling every 3 seconds
|
||||||
const { value: liveData } = useAsyncInterval(
|
const { value: liveData } = useAsyncInterval(
|
||||||
|
"liveNodes",
|
||||||
{ cpuUsage: 0, ramUsage: 0 },
|
{ cpuUsage: 0, ramUsage: 0 },
|
||||||
async () => {
|
async () => {
|
||||||
const nodes = await getLiveNodes();
|
const nodes = await getLiveNodes();
|
||||||
@@ -44,11 +51,12 @@ export default function MonitoringPage() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Container stats
|
// Container stats
|
||||||
const { value: containerStats } = useAsync(
|
const { value: containerStats } = useAsync(
|
||||||
|
"containerStats",
|
||||||
[],
|
[],
|
||||||
async () => {
|
async () => {
|
||||||
const nodes = await getNodes();
|
const nodes = await getNodes();
|
||||||
@@ -61,18 +69,18 @@ export default function MonitoringPage() {
|
|||||||
const stats = await getContainerStats(c.Id);
|
const stats = await getContainerStats(c.Id);
|
||||||
if (!stats) return undefined;
|
if (!stats) return undefined;
|
||||||
return { ...stats, name: c.Names[0] };
|
return { ...stats, name: c.Names[0] };
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
).filter((s): s is SimpleStats & { name: string } => s !== undefined);
|
).filter((s): s is SimpleStats & { name: string } => s !== undefined);
|
||||||
},
|
},
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const containerCPU = containerStats.reduce((acc, val) => acc + val.cpu, 0);
|
const containerCPU = containerStats.reduce((acc, val) => acc + val.cpu, 0);
|
||||||
const containerMemory = containerStats.reduce(
|
const containerMemory = containerStats.reduce(
|
||||||
(acc, val) => acc + val.memory,
|
(acc, val) => acc + val.memory,
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -35,14 +35,18 @@ function DiskUsage({ liveData }: NodeLiveData) {
|
|||||||
|
|
||||||
export default function NodesPage() {
|
export default function NodesPage() {
|
||||||
const { value: nodes, setValue: setNodes } = useAsyncInterval(
|
const { value: nodes, setValue: setNodes } = useAsyncInterval(
|
||||||
|
"liveNodes",
|
||||||
[],
|
[],
|
||||||
getLiveNodes,
|
getLiveNodes,
|
||||||
3000
|
3000,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { value: dockerNodes } = useAsync([], async () => getDocker(nodes), [
|
const { value: dockerNodes } = useAsync(
|
||||||
nodes,
|
"docker",
|
||||||
]);
|
[],
|
||||||
|
async () => getDocker(nodes),
|
||||||
|
[nodes],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout title="Nodes" subtitle="Cluster node management">
|
<DashboardLayout title="Nodes" subtitle="Cluster node management">
|
||||||
|
|||||||
@@ -17,12 +17,21 @@ import { getDeployments } from "@/lib/dockship/deploy";
|
|||||||
import { useAsync, useAsyncInterval } from "@/hooks/use-async";
|
import { useAsync, useAsyncInterval } from "@/hooks/use-async";
|
||||||
|
|
||||||
export default function OverviewPage() {
|
export default function OverviewPage() {
|
||||||
const { value: nodes } = useAsyncInterval([], getLiveNodes, 3000);
|
const { value: nodes } = useAsyncInterval(
|
||||||
const { value: dockerNodes } = useAsync([], async () => getDocker(nodes), [
|
"liveNodes",
|
||||||
nodes,
|
[],
|
||||||
]);
|
getLiveNodes,
|
||||||
const { value: apps } = useAsyncInterval([], getApps, 3000);
|
3000,
|
||||||
|
);
|
||||||
|
const { value: dockerNodes } = useAsync(
|
||||||
|
"docker",
|
||||||
|
[],
|
||||||
|
async () => getDocker(nodes),
|
||||||
|
[nodes],
|
||||||
|
);
|
||||||
|
const { value: apps } = useAsyncInterval("apps", [], getApps, 3000);
|
||||||
const { value: deployments } = useAsyncInterval(
|
const { value: deployments } = useAsyncInterval(
|
||||||
|
"deployments",
|
||||||
[],
|
[],
|
||||||
async () => (await getDeployments()).filter((d) => d !== null),
|
async () => (await getDeployments()).filter((d) => d !== null),
|
||||||
3000,
|
3000,
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export default function DeployAppDialog({
|
|||||||
defaultApp?: string;
|
defaultApp?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const { value: nodes } = useAsync([], getLiveNodes);
|
const { value: nodes } = useAsync("liveNodes", [], getLiveNodes);
|
||||||
const { value: apps } = useAsync<Application[]>([], getApps);
|
const { value: apps } = useAsync<Application[]>("apps", [], getApps);
|
||||||
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("");
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ import {
|
|||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
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 { Combobox } from "../ui/combobox";
|
|
||||||
import { registerApp } from "@/lib/dockship/application";
|
import { registerApp } from "@/lib/dockship/application";
|
||||||
import { useAsync } from "@/hooks/use-async";
|
|
||||||
|
|
||||||
export default function RegisterAppDialog({
|
export default function RegisterAppDialog({
|
||||||
children,
|
children,
|
||||||
|
|||||||
+36
-18
@@ -1,20 +1,31 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
export function useAsync<T>(
|
export function useAsync<T>(
|
||||||
|
cacheKey: string,
|
||||||
defaultValue: T,
|
defaultValue: T,
|
||||||
asyncFunction: () => Promise<T>,
|
asyncFunction: () => Promise<T>,
|
||||||
dependencies: any[] = []
|
dependencies: any[] = [],
|
||||||
) {
|
) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<null | Error>(null);
|
const [error, setError] = useState<null | Error>(null);
|
||||||
const [value, setValue] = useState<T>(defaultValue);
|
const [value, setValue] = useState<T>(() => {
|
||||||
|
try {
|
||||||
|
const cached = localStorage.getItem(cacheKey);
|
||||||
|
return cached ? (JSON.parse(cached) as T) : defaultValue;
|
||||||
|
} catch {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
asyncFunction()
|
asyncFunction()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (isMounted) setValue(result);
|
if (!isMounted) return;
|
||||||
|
setValue(result);
|
||||||
|
localStorage.setItem(cacheKey, JSON.stringify(result));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (isMounted) setError(err);
|
if (isMounted) setError(err);
|
||||||
@@ -32,35 +43,42 @@ export function useAsync<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useAsyncInterval<T>(
|
export function useAsyncInterval<T>(
|
||||||
|
cacheKey: string,
|
||||||
defaultValue: T,
|
defaultValue: T,
|
||||||
asyncFunction: () => Promise<T>,
|
asyncFunction: () => Promise<T>,
|
||||||
interval: number,
|
interval: number,
|
||||||
dependencies: any[] = []
|
dependencies: any[] = [],
|
||||||
) {
|
) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<null | Error>(null);
|
const [error, setError] = useState<null | Error>(null);
|
||||||
const [value, setValue] = useState<T>(defaultValue);
|
const [value, setValue] = useState<T>(() => {
|
||||||
|
try {
|
||||||
|
const cached = localStorage.getItem(cacheKey);
|
||||||
|
return cached ? (JSON.parse(cached) as T) : defaultValue;
|
||||||
|
} catch {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
|
||||||
const run = () => {
|
const run = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
asyncFunction()
|
try {
|
||||||
.then((result) => {
|
const result = await asyncFunction();
|
||||||
if (isMounted) setValue(result);
|
if (!isMounted) return;
|
||||||
})
|
setValue(result);
|
||||||
.catch((err) => {
|
localStorage.setItem(cacheKey, JSON.stringify(result));
|
||||||
if (isMounted) setError(err);
|
} catch (err) {
|
||||||
})
|
if (isMounted) setError(err as Error);
|
||||||
.finally(() => {
|
} finally {
|
||||||
if (isMounted) setLoading(false);
|
if (isMounted) setLoading(false);
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const iid = setInterval(run, interval);
|
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
const iid = setInterval(run, interval);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user