diff --git a/ui/src/app/jobs/new/page.tsx b/ui/src/app/jobs/new/page.tsx
index 0633f31e..527334ed 100644
--- a/ui/src/app/jobs/new/page.tsx
+++ b/ui/src/app/jobs/new/page.tsx
@@ -64,7 +64,7 @@ export default function TrainingForm() {
useEffect(() => {
if (isGPUInfoLoaded) {
if (gpuIDs === null && gpuList.length > 0) {
- setGpuIDs(`${gpuList[0]}`);
+ setGpuIDs(`${gpuList[0].index}`);
}
}
}, [gpuList, isGPUInfoLoaded]);
@@ -154,7 +154,7 @@ export default function TrainingForm() {
value={`${gpuIDs}`}
className="pt-2"
onChange={value => setGpuIDs(value)}
- options={gpuList.map(gpu => ({ value: `${gpu}`, label: `GPU #${gpu}` }))}
+ options={gpuList.map(gpu => ({ value: `${gpu.index}`, label: `GPU #${gpu.index}` }))}
/>
diff --git a/ui/src/components/GPUWidget.tsx b/ui/src/components/GPUWidget.tsx
index e9f219b1..adb71ea7 100644
--- a/ui/src/components/GPUWidget.tsx
+++ b/ui/src/components/GPUWidget.tsx
@@ -28,7 +28,6 @@ export default function GPUWidget({ gpu }: GPUWidgetProps) {
#{gpu.index}
-
diff --git a/ui/src/components/JobOverview.tsx b/ui/src/components/JobOverview.tsx
index 3df5152c..61c48b84 100644
--- a/ui/src/components/JobOverview.tsx
+++ b/ui/src/components/JobOverview.tsx
@@ -1,23 +1,99 @@
import { Job } from '@prisma/client';
+import useGPUInfo from '@/hooks/useGPUInfo';
+import GPUWidget from '@/components/GPUWidget';
+import { getJobConfig, getTotalSteps } from '@/utils/jobs';
+import { Cpu, HardDrive, Info } from 'lucide-react';
interface JobOverviewProps {
job: Job;
}
export default function JobOverview({ job }: JobOverviewProps) {
+ const gpuIds = job.gpu_ids.split(',').map(id => parseInt(id));
+ const { gpuList, isGPUInfoLoaded } = useGPUInfo(gpuIds);
+ const totalSteps = getTotalSteps(job);
+ const progress = (job.step / totalSteps) * 100;
+ const isStopping = job.stop && job.status === 'running';
+
+ const getStatusColor = (status: string) => {
+ switch (status.toLowerCase()) {
+ case 'running': return 'bg-emerald-500/10 text-emerald-500';
+ case 'stopping': return 'bg-amber-500/10 text-amber-500';
+ case 'stopped': return 'bg-gray-500/10 text-gray-400';
+ case 'completed': return 'bg-blue-500/10 text-blue-500';
+ case 'error': return 'bg-rose-500/10 text-rose-500';
+ default: return 'bg-gray-500/10 text-gray-400';
+ }
+ };
+
+ let status = job.status;
+ if (isStopping) {
+ status = 'stopping';
+ }
+
return (
- <>
-
-
-
Job Details
-
ID: {job.id}
-
Name: {job.name}
-
GPUs: {job.gpu_ids}
-
Status: {job.status}
-
Info: {job.info}
-
Step: {job.step}
+
+ {/* Job Information Panel */}
+
+
+
Job Details
+
+ {job.status}
+
+
+
+
+ {/* Progress Bar */}
+
+
+ Progress
+
+ Step {job.step} of {totalSteps}
+
+
+
+
+
+ {/* Job Info Grid */}
+
+
+
+
+
{job.name}
+
Job Name
+
+
+
+
+
+
+
GPUs: {job.gpu_ids}
+
Assigned GPUs
+
+
+
+
+
+
+
{job.info}
+
Additional Information
+
+
+
- >
+
+ {/* GPU Widget Panel */}
+
+ {isGPUInfoLoaded && gpuList.length > 0 && (
+
+ )}
+
+
);
-}
+}
\ No newline at end of file
diff --git a/ui/src/hooks/useGPUInfo.tsx b/ui/src/hooks/useGPUInfo.tsx
index dd7e32fe..f8474701 100644
--- a/ui/src/hooks/useGPUInfo.tsx
+++ b/ui/src/hooks/useGPUInfo.tsx
@@ -1,10 +1,10 @@
'use client';
-import { GPUApiResponse } from '@/types';
+import { GPUApiResponse, GpuInfo } from '@/types';
import { useEffect, useState } from 'react';
-export default function useGPUInfo() {
- const [gpuList, setGpuList] = useState
([]);
+export default function useGPUInfo(gpuIds: null | number[] = null) {
+ const [gpuList, setGpuList] = useState([]);
const [isGPUInfoLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const fetchGpuInfo = async () => {
@@ -16,7 +16,12 @@ export default function useGPUInfo() {
}
const data: GPUApiResponse = await response.json();
- setGpuList(data.gpus.map(gpu => gpu.index).sort());
+ let gpus = data.gpus.sort((a, b) => a.index - b.index);
+ if (gpuIds) {
+ gpus = gpus.filter(gpu => gpuIds.includes(gpu.index));
+ }
+
+ setGpuList(gpus);
} catch (err) {
console.log(`Failed to fetch GPU data: ${err instanceof Error ? err.message : String(err)}`);
} finally {