refactor: gpu support component and related api routers; update template environment variables

This commit is contained in:
vishalkadam47
2024-11-05 12:07:35 +05:30
parent 3b5e8921d0
commit b53da82204
3 changed files with 249 additions and 211 deletions

View File

@@ -1,219 +1,260 @@
import { Button } from '@/components/ui/button'; import { AlertBlock } from "@/components/shared/alert-block";
import { useState } from 'react'; import { DialogAction } from "@/components/shared/dialog-action";
import { api } from '@/utils/api'; import { Button } from "@/components/ui/button";
import { toast } from 'sonner'; import {
import { TRPCClientError } from '@trpc/client'; Card,
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; CardContent,
import { DialogAction } from '@/components/shared/dialog-action'; CardDescription,
import { AlertBlock } from '@/components/shared/alert-block'; CardHeader,
import { Cpu, CheckCircle2, XCircle, Loader2 } from 'lucide-react'; CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { TRPCClientError } from "@trpc/client";
import { CheckCircle2, Cpu, Loader2, XCircle } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
interface GPUSupportProps { interface GPUSupportProps {
serverId?: string; serverId?: string;
} }
export function GPUSupport({ serverId }: GPUSupportProps) { export function GPUSupport({ serverId }: GPUSupportProps) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const utils = api.useContext(); const utils = api.useContext();
const { data: gpuStatus, isLoading: isChecking } = api.settings.checkGPUStatus.useQuery( const { data: gpuStatus, isLoading: isChecking } =
{ serverId }, api.settings.checkGPUStatus.useQuery(
{ { serverId },
enabled: !!serverId, {
refetchInterval: 5000 enabled: !!serverId,
} refetchInterval: 5000,
); },
);
const setupGPU = api.settings.setupGPU.useMutation({ const setupGPU = api.settings.setupGPU.useMutation({
onMutate: () => { onMutate: () => {
setIsLoading(true); setIsLoading(true);
}, },
onSuccess: async () => { onSuccess: async () => {
toast.success('GPU support enabled successfully'); toast.success("GPU support enabled successfully");
setIsLoading(false); setIsLoading(false);
await Promise.all([ await Promise.all([
utils.settings.checkGPUStatus.invalidate({ serverId }), utils.settings.checkGPUStatus.invalidate({ serverId }),
utils.server.invalidate() utils.server.invalidate(),
]); ]);
}, },
onError: (error) => { onError: (error) => {
if (error instanceof TRPCClientError) { if (error instanceof TRPCClientError) {
const errorMessage = error.message; const errorMessage = error.message;
if (errorMessage.includes('permission denied')) { if (errorMessage.includes("permission denied")) {
toast.error('Permission denied. Please ensure proper sudo access.'); toast.error("Permission denied. Please ensure proper sudo access.");
} else if (errorMessage.includes('Failed to configure GPU')) { } else if (errorMessage.includes("Failed to configure GPU")) {
toast.error('GPU configuration failed. Please check system requirements.'); toast.error(
} else { "GPU configuration failed. Please check system requirements.",
toast.error(errorMessage); );
} } else {
} else { toast.error(errorMessage);
toast.error('Failed to enable GPU support. Please check server logs.'); }
} } else {
toast.error("Failed to enable GPU support. Please check server logs.");
}
setIsLoading(false); setIsLoading(false);
} },
}); });
const handleEnableGPU = async () => { const handleEnableGPU = async () => {
if (!serverId) { if (!serverId) {
toast.error('No server selected'); toast.error("No server selected");
return; return;
} }
try { try {
await setupGPU.mutateAsync({ serverId }); await setupGPU.mutateAsync({ serverId });
} catch (error) { } catch (error) {
// Error handling is done in mutation's onError // Error handling is done in mutation's onError
} }
}; };
return ( return (
<CardContent className="p-0"> <CardContent className="p-0">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<Card className="bg-background"> <Card className="bg-background">
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2"> <CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
<div className="flex flex-row gap-2 justify-between w-full items-end max-sm:flex-col"> <div className="flex flex-row gap-2 justify-between w-full items-end max-sm:flex-col">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cpu className="size-5" /> <Cpu className="size-5" />
<CardTitle className="text-xl">GPU Configuration</CardTitle> <CardTitle className="text-xl">GPU Configuration</CardTitle>
</div> </div>
<CardDescription>Configure and monitor GPU support</CardDescription> <CardDescription>
</div> Configure and monitor GPU support
<DialogAction </CardDescription>
title="Enable GPU Support?" </div>
description="This will enable GPU support for Docker Swarm on this server. Make sure you have the required hardware and drivers installed." <DialogAction
onClick={handleEnableGPU} title="Enable GPU Support?"
> description="This will enable GPU support for Docker Swarm on this server. Make sure you have the required hardware and drivers installed."
<Button onClick={handleEnableGPU}
isLoading={isLoading} >
disabled={isLoading || !serverId || isChecking} <Button
> isLoading={isLoading}
{isLoading ? ( disabled={isLoading || !serverId || isChecking}
'Enabling GPU...' >
) : gpuStatus?.swarmEnabled ? ( {isLoading
'Reconfigure GPU' ? "Enabling GPU..."
) : ( : gpuStatus?.swarmEnabled
'Enable GPU' ? "Reconfigure GPU"
)} : "Enable GPU"}
</Button> </Button>
</DialogAction> </DialogAction>
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="flex flex-col gap-4"> <CardContent className="flex flex-col gap-4">
<AlertBlock type="info"> <AlertBlock type="info">
<div className="font-medium mb-2">System Requirements:</div> <div className="font-medium mb-2">System Requirements:</div>
<ul className="list-disc list-inside text-sm space-y-1"> <ul className="list-disc list-inside text-sm space-y-1">
<li>NVIDIA drivers must be installed on the host system</li> <li>NVIDIA drivers must be installed on the host system</li>
<li>NVIDIA Container Runtime is required for GPU support</li> <li>NVIDIA Container Runtime is required for GPU support</li>
<li>Compatible GPU hardware must be present</li> <li>Compatible GPU hardware must be present</li>
</ul> </ul>
</AlertBlock> </AlertBlock>
{isChecking ? ( {isChecking ? (
<div className="flex items-center justify-center text-muted-foreground py-4"> <div className="flex items-center justify-center text-muted-foreground py-4">
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
<span>Checking GPU status...</span> <span>Checking GPU status...</span>
</div> </div>
) : ( ) : (
<div className="grid gap-4"> <div className="grid gap-4">
{/* Prerequisites Section */} {/* Prerequisites Section */}
<div className="border rounded-lg p-4"> <div className="border rounded-lg p-4">
<h3 className="text-lg font-semibold mb-1">Prerequisites</h3> <h3 className="text-lg font-semibold mb-1">Prerequisites</h3>
<p className="text-sm text-muted-foreground mb-4">Shows all software checks and available hardware</p> <p className="text-sm text-muted-foreground mb-4">
<div className="grid gap-2.5"> Shows all software checks and available hardware
<StatusRow </p>
label="NVIDIA Driver" <div className="grid gap-2.5">
isEnabled={gpuStatus?.driverInstalled} <StatusRow
description={gpuStatus?.driverVersion ? `Installed (v${gpuStatus.driverVersion})` : 'Not Installed'} label="NVIDIA Driver"
/> isEnabled={gpuStatus?.driverInstalled}
<StatusRow description={
label="GPU Model" gpuStatus?.driverVersion
value={gpuStatus?.gpuModel || 'Not Detected'} ? `Installed (v${gpuStatus.driverVersion})`
showIcon={false} : "Not Installed"
/> }
<StatusRow />
label="GPU Memory" <StatusRow
value={gpuStatus?.memoryInfo || 'Not Available'} label="GPU Model"
showIcon={false} value={gpuStatus?.gpuModel || "Not Detected"}
/> showIcon={false}
<StatusRow />
label="Available GPUs" <StatusRow
value={gpuStatus?.availableGPUs || 0} label="GPU Memory"
showIcon={false} value={gpuStatus?.memoryInfo || "Not Available"}
/> showIcon={false}
<StatusRow />
label="CUDA Support" <StatusRow
isEnabled={gpuStatus?.cudaSupport} label="Available GPUs"
description={gpuStatus?.cudaVersion ? `Available (v${gpuStatus.cudaVersion})` : 'Not Available'} value={gpuStatus?.availableGPUs || 0}
/> showIcon={false}
<StatusRow />
label="NVIDIA Container Runtime" <StatusRow
isEnabled={gpuStatus?.runtimeInstalled} label="CUDA Support"
description={gpuStatus?.runtimeInstalled ? 'Installed' : 'Not Installed'} isEnabled={gpuStatus?.cudaSupport}
/> description={
</div> gpuStatus?.cudaVersion
</div> ? `Available (v${gpuStatus.cudaVersion})`
: "Not Available"
}
/>
<StatusRow
label="NVIDIA Container Runtime"
isEnabled={gpuStatus?.runtimeInstalled}
description={
gpuStatus?.runtimeInstalled
? "Installed"
: "Not Installed"
}
/>
</div>
</div>
{/* Configuration Status */} {/* Configuration Status */}
<div className="border rounded-lg p-4"> <div className="border rounded-lg p-4">
<h3 className="text-lg font-semibold mb-1">Docker Swarm GPU Status</h3> <h3 className="text-lg font-semibold mb-1">
<p className="text-sm text-muted-foreground mb-4">Shows the configuration state that changes with the Enable GPU</p> Docker Swarm GPU Status
<div className="grid gap-2.5"> </h3>
<StatusRow <p className="text-sm text-muted-foreground mb-4">
label="Runtime Configuration" Shows the configuration state that changes with the Enable
isEnabled={gpuStatus?.runtimeConfigured} GPU
description={gpuStatus?.runtimeConfigured ? 'Default Runtime' : 'Not Default Runtime'} </p>
/> <div className="grid gap-2.5">
<StatusRow <StatusRow
label="Swarm GPU Support" label="Runtime Configuration"
isEnabled={gpuStatus?.swarmEnabled} isEnabled={gpuStatus?.runtimeConfigured}
description={gpuStatus?.swarmEnabled description={
? `Enabled (${gpuStatus.gpuResources} GPU${gpuStatus.gpuResources !== 1 ? 's' : ''})` gpuStatus?.runtimeConfigured
: 'Not Enabled' ? "Default Runtime"
} : "Not Default Runtime"
/> }
</div> />
</div> <StatusRow
</div> label="Swarm GPU Support"
)} isEnabled={gpuStatus?.swarmEnabled}
</CardContent> description={
</Card> gpuStatus?.swarmEnabled
</div> ? `Enabled (${gpuStatus.gpuResources} GPU${gpuStatus.gpuResources !== 1 ? "s" : ""})`
</CardContent> : "Not Enabled"
); }
/>
</div>
</div>
</div>
)}
</CardContent>
</Card>
</div>
</CardContent>
);
} }
interface StatusRowProps { interface StatusRowProps {
label: string; label: string;
isEnabled?: boolean; isEnabled?: boolean;
description?: string; description?: string;
value?: string | number; value?: string | number;
showIcon?: boolean; showIcon?: boolean;
} }
function StatusRow({ label, isEnabled, description, value, showIcon = true }: StatusRowProps) { function StatusRow({
return ( label,
<div className="flex items-center justify-between"> isEnabled,
<span className="text-sm">{label}</span> description,
<div className="flex items-center gap-2"> value,
{showIcon ? ( showIcon = true,
<> }: StatusRowProps) {
{isEnabled ? ( return (
<CheckCircle2 className="size-4 text-green-500" /> <div className="flex items-center justify-between">
) : ( <span className="text-sm">{label}</span>
<XCircle className="size-4 text-red-500" /> <div className="flex items-center gap-2">
)} {showIcon ? (
<span className={`text-sm ${isEnabled ? 'text-green-500' : 'text-red-500'}`}> <>
{description || (isEnabled ? 'Installed' : 'Not Installed')} {isEnabled ? (
</span> <CheckCircle2 className="size-4 text-green-500" />
</> ) : (
) : ( <XCircle className="size-4 text-red-500" />
<span className="text-sm text-muted-foreground">{value}</span> )}
)} <span
</div> className={`text-sm ${isEnabled ? "text-green-500" : "text-red-500"}`}
</div> >
); {description || (isEnabled ? "Installed" : "Not Installed")}
</span>
</>
) : (
<span className="text-sm text-muted-foreground">{value}</span>
)}
</div>
</div>
);
} }

View File

@@ -52,10 +52,7 @@ import {
writeMainConfig, writeMainConfig,
writeTraefikConfigInPath, writeTraefikConfigInPath,
} from "@dokploy/server"; } from "@dokploy/server";
import { import { checkGPUStatus, setupGPUSupport } from "@dokploy/server";
checkGPUStatus,
setupGPUSupport,
} from "@dokploy/server";
import { generateOpenApiDocument } from "@dokploy/trpc-openapi"; import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { sql } from "drizzle-orm"; import { sql } from "drizzle-orm";

View File

@@ -19,12 +19,12 @@ export function generate(schema: Schema): Template {
]; ];
const envs = [ const envs = [
`PUID=1000`, "PUID=1000",
`PGID=1000`, "PGID=1000",
`TZ=Etc/UTC`, "TZ=Etc/UTC",
`SUBFOLDER=/`, "SUBFOLDER=/",
`NVIDIA_VISIBLE_DEVICES=all`, "NVIDIA_VISIBLE_DEVICES=all",
`NVIDIA_DRIVER_CAPABILITIES=all`, "NVIDIA_DRIVER_CAPABILITIES=all",
]; ];
return { return {