more-compute 0.1.3__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frontend/app/globals.css +322 -77
- frontend/app/layout.tsx +98 -82
- frontend/components/Cell.tsx +234 -95
- frontend/components/Notebook.tsx +430 -199
- frontend/components/{AddCellButton.tsx → cell/AddCellButton.tsx} +0 -2
- frontend/components/cell/MonacoCell.tsx +726 -0
- frontend/components/layout/ConnectionBanner.tsx +41 -0
- frontend/components/{Sidebar.tsx → layout/Sidebar.tsx} +16 -11
- frontend/components/modals/ConfirmModal.tsx +154 -0
- frontend/components/modals/SuccessModal.tsx +140 -0
- frontend/components/output/MarkdownRenderer.tsx +116 -0
- frontend/components/popups/ComputePopup.tsx +674 -365
- frontend/components/popups/MetricsPopup.tsx +11 -7
- frontend/components/popups/SettingsPopup.tsx +11 -13
- frontend/contexts/PodWebSocketContext.tsx +247 -0
- frontend/eslint.config.mjs +11 -0
- frontend/lib/monaco-themes.ts +160 -0
- frontend/lib/settings.ts +128 -26
- frontend/lib/themes.json +9973 -0
- frontend/lib/websocket-native.ts +19 -8
- frontend/lib/websocket.ts +59 -11
- frontend/next.config.ts +8 -0
- frontend/package-lock.json +1705 -3
- frontend/package.json +8 -1
- frontend/styling_README.md +18 -0
- kernel_run.py +161 -43
- more_compute-0.2.0.dist-info/METADATA +126 -0
- more_compute-0.2.0.dist-info/RECORD +100 -0
- morecompute/__version__.py +1 -0
- morecompute/execution/executor.py +31 -20
- morecompute/execution/worker.py +68 -7
- morecompute/models/__init__.py +31 -0
- morecompute/models/api_models.py +197 -0
- morecompute/notebook.py +50 -7
- morecompute/server.py +574 -94
- morecompute/services/data_manager.py +379 -0
- morecompute/services/lsp_service.py +335 -0
- morecompute/services/pod_manager.py +122 -20
- morecompute/services/pod_monitor.py +138 -0
- morecompute/services/prime_intellect.py +87 -63
- morecompute/utils/config_util.py +59 -0
- morecompute/utils/special_commands.py +11 -5
- morecompute/utils/zmq_util.py +51 -0
- frontend/components/MarkdownRenderer.tsx +0 -84
- frontend/components/popups/PythonPopup.tsx +0 -292
- more_compute-0.1.3.dist-info/METADATA +0 -173
- more_compute-0.1.3.dist-info/RECORD +0 -85
- /frontend/components/{CellButton.tsx → cell/CellButton.tsx} +0 -0
- /frontend/components/{ErrorModal.tsx → modals/ErrorModal.tsx} +0 -0
- /frontend/components/{CellOutput.tsx → output/CellOutput.tsx} +0 -0
- /frontend/components/{ErrorDisplay.tsx → output/ErrorDisplay.tsx} +0 -0
- {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/WHEEL +0 -0
- {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/entry_points.txt +0 -0
- {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
1
|
+
import React, { useState, useEffect, useMemo } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Zap,
|
|
4
4
|
ExternalLink,
|
|
@@ -24,8 +24,11 @@ import {
|
|
|
24
24
|
CreatePodRequest,
|
|
25
25
|
PodConnectionStatus,
|
|
26
26
|
} from "@/lib/api";
|
|
27
|
-
import ErrorModal from "@/components/ErrorModal";
|
|
27
|
+
import ErrorModal from "@/components/modals/ErrorModal";
|
|
28
|
+
import SuccessModal from "@/components/modals/SuccessModal";
|
|
29
|
+
import ConfirmModal from "@/components/modals/ConfirmModal";
|
|
28
30
|
import FilterPopup from "./FilterPopup";
|
|
31
|
+
import { usePodWebSocket } from "@/contexts/PodWebSocketContext";
|
|
29
32
|
|
|
30
33
|
interface GPUPod {
|
|
31
34
|
id: string;
|
|
@@ -34,6 +37,7 @@ interface GPUPod {
|
|
|
34
37
|
gpuType: string;
|
|
35
38
|
region: string;
|
|
36
39
|
costPerHour: number;
|
|
40
|
+
sshConnection: string | null;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
interface ComputePopupProps {
|
|
@@ -41,7 +45,17 @@ interface ComputePopupProps {
|
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
44
|
-
const
|
|
48
|
+
const {
|
|
49
|
+
gpuPods,
|
|
50
|
+
setPods,
|
|
51
|
+
registerAutoConnect,
|
|
52
|
+
connectionState,
|
|
53
|
+
setConnectionState,
|
|
54
|
+
connectingPodId,
|
|
55
|
+
setConnectingPodId,
|
|
56
|
+
connectedPodId,
|
|
57
|
+
setConnectedPodId,
|
|
58
|
+
} = usePodWebSocket();
|
|
45
59
|
const [loading, setLoading] = useState(false);
|
|
46
60
|
const [kernelStatus, setKernelStatus] = useState(false);
|
|
47
61
|
const [apiConfigured, setApiConfigured] = useState<boolean | null>(null);
|
|
@@ -56,9 +70,10 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
56
70
|
const [filters, setFilters] = useState<GpuAvailabilityParams>({});
|
|
57
71
|
const [creatingPodId, setCreatingPodId] = useState<string | null>(null);
|
|
58
72
|
const [podCreationError, setPodCreationError] = useState<string | null>(null);
|
|
59
|
-
const [connectingPodId, setConnectingPodId] = useState<string | null>(null);
|
|
60
|
-
const [connectedPodId, setConnectedPodId] = useState<string | null>(null);
|
|
61
73
|
const [deletingPodId, setDeletingPodId] = useState<string | null>(null);
|
|
74
|
+
const [connectionHealth, setConnectionHealth] = useState<"healthy" | "unhealthy" | "unknown">("unknown");
|
|
75
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
76
|
+
const [discoveredPod, setDiscoveredPod] = useState<GPUPod | null>(null);
|
|
62
77
|
|
|
63
78
|
// Filter popup state
|
|
64
79
|
const [showFilterPopup, setShowFilterPopup] = useState(false);
|
|
@@ -76,6 +91,63 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
76
91
|
message: "",
|
|
77
92
|
});
|
|
78
93
|
|
|
94
|
+
// Success modal state
|
|
95
|
+
const [successModal, setSuccessModal] = useState<{
|
|
96
|
+
isOpen: boolean;
|
|
97
|
+
title: string;
|
|
98
|
+
message: string;
|
|
99
|
+
}>({
|
|
100
|
+
isOpen: false,
|
|
101
|
+
title: "",
|
|
102
|
+
message: "",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Confirm modal state
|
|
106
|
+
const [confirmModal, setConfirmModal] = useState<{
|
|
107
|
+
isOpen: boolean;
|
|
108
|
+
title: string;
|
|
109
|
+
message: string;
|
|
110
|
+
onConfirm: () => void;
|
|
111
|
+
}>({
|
|
112
|
+
isOpen: false,
|
|
113
|
+
title: "",
|
|
114
|
+
message: "",
|
|
115
|
+
onConfirm: () => {},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Health check effect - runs every 10 seconds when connected
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!connectedPodId) {
|
|
121
|
+
setConnectionHealth("unknown");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const checkConnectionHealth = async () => {
|
|
126
|
+
try {
|
|
127
|
+
const status: PodConnectionStatus = await getPodConnectionStatus();
|
|
128
|
+
if (status.connected && status.pod?.id === connectedPodId) {
|
|
129
|
+
setConnectionHealth("healthy");
|
|
130
|
+
} else {
|
|
131
|
+
setConnectionHealth("unhealthy");
|
|
132
|
+
// Auto-disconnect if connection is dead
|
|
133
|
+
setConnectedPodId(null);
|
|
134
|
+
setKernelStatus(false);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error("Connection health check failed:", err);
|
|
138
|
+
setConnectionHealth("unhealthy");
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Initial check
|
|
143
|
+
checkConnectionHealth();
|
|
144
|
+
|
|
145
|
+
// Set up periodic health checks
|
|
146
|
+
const interval = setInterval(checkConnectionHealth, 10000); // Check every 10 seconds
|
|
147
|
+
|
|
148
|
+
return () => clearInterval(interval);
|
|
149
|
+
}, [connectedPodId]);
|
|
150
|
+
|
|
79
151
|
useEffect(() => {
|
|
80
152
|
const checkApiConfig = async () => {
|
|
81
153
|
try {
|
|
@@ -83,11 +155,32 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
83
155
|
setApiConfigured(config.configured);
|
|
84
156
|
if (config.configured) {
|
|
85
157
|
await loadGPUPods();
|
|
158
|
+
// Load available GPUs automatically
|
|
159
|
+
await loadAvailableGPUs();
|
|
86
160
|
// Check if already connected to a pod
|
|
87
161
|
const status: PodConnectionStatus = await getPodConnectionStatus();
|
|
88
162
|
if (status.connected && status.pod) {
|
|
89
163
|
setConnectedPodId(status.pod.id);
|
|
90
164
|
setKernelStatus(true); // Kernel is running when connected to pod
|
|
165
|
+
setConnectionHealth("healthy");
|
|
166
|
+
} else if (!status.connected && status.pod) {
|
|
167
|
+
// Discovered running pod but not connected (backend restart scenario)
|
|
168
|
+
console.log("[COMPUTE POPUP] Discovered running pod after restart:", status.pod);
|
|
169
|
+
// Only show discovered pod warning if it's not already in the pods list
|
|
170
|
+
// (This can happen if pods haven't loaded yet)
|
|
171
|
+
const pod = status.pod;
|
|
172
|
+
const alreadyInList = gpuPods.some(p => p.id === pod.id);
|
|
173
|
+
if (!alreadyInList) {
|
|
174
|
+
setDiscoveredPod({
|
|
175
|
+
id: pod.id,
|
|
176
|
+
name: pod.name,
|
|
177
|
+
status: "running",
|
|
178
|
+
gpuType: pod.gpu_type || "Unknown",
|
|
179
|
+
region: "Unknown",
|
|
180
|
+
costPerHour: pod.price_hr || 0,
|
|
181
|
+
sshConnection: pod.ssh_connection || null,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
91
184
|
}
|
|
92
185
|
}
|
|
93
186
|
} catch (err) {
|
|
@@ -96,16 +189,7 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
96
189
|
}
|
|
97
190
|
};
|
|
98
191
|
checkApiConfig();
|
|
99
|
-
|
|
100
|
-
// Poll pod list every 10 seconds if configured
|
|
101
|
-
const pollInterval = setInterval(async () => {
|
|
102
|
-
if (apiConfigured) {
|
|
103
|
-
await loadGPUPods();
|
|
104
|
-
}
|
|
105
|
-
}, 10000);
|
|
106
|
-
|
|
107
|
-
return () => clearInterval(pollInterval);
|
|
108
|
-
}, [apiConfigured]);
|
|
192
|
+
}, []);
|
|
109
193
|
|
|
110
194
|
const loadGPUPods = async (params?: PodsListParams) => {
|
|
111
195
|
setLoading(true);
|
|
@@ -113,10 +197,11 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
113
197
|
const response = await fetchGpuPods(params || { limit: 100 });
|
|
114
198
|
const pods = (response.data || []).map((pod: PodResponse) => {
|
|
115
199
|
// Map API status to UI status
|
|
200
|
+
// Pod must be ACTIVE *and* have SSH connection info to be "running"
|
|
116
201
|
let uiStatus: "running" | "stopped" | "starting" = "stopped";
|
|
117
|
-
if (pod.status === "ACTIVE") {
|
|
202
|
+
if (pod.status === "ACTIVE" && pod.sshConnection) {
|
|
118
203
|
uiStatus = "running";
|
|
119
|
-
} else if (pod.status === "PROVISIONING" || pod.status === "PENDING") {
|
|
204
|
+
} else if (pod.status === "ACTIVE" || pod.status === "PROVISIONING" || pod.status === "PENDING") {
|
|
120
205
|
uiStatus = "starting";
|
|
121
206
|
}
|
|
122
207
|
|
|
@@ -127,9 +212,10 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
127
212
|
gpuType: pod.gpuName,
|
|
128
213
|
region: "Unknown", //look at later
|
|
129
214
|
costPerHour: pod.priceHr,
|
|
215
|
+
sshConnection: pod.sshConnection,
|
|
130
216
|
};
|
|
131
217
|
});
|
|
132
|
-
|
|
218
|
+
setPods(pods);
|
|
133
219
|
} catch (err) {
|
|
134
220
|
console.error("Failed to load GPU pods:", err);
|
|
135
221
|
} finally {
|
|
@@ -187,14 +273,23 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
187
273
|
|
|
188
274
|
const newPod = await createGpuPod(podRequest);
|
|
189
275
|
|
|
276
|
+
// Register auto-connect callback for when pod becomes ready
|
|
277
|
+
registerAutoConnect(newPod.id, handleConnectToPod);
|
|
278
|
+
|
|
279
|
+
// Set provisioning state for the banner
|
|
280
|
+
setConnectingPodId(newPod.id);
|
|
281
|
+
setConnectionState("provisioning"); // Show "PROVISIONING" banner
|
|
282
|
+
|
|
190
283
|
// Refresh the pods list
|
|
191
284
|
await loadGPUPods();
|
|
192
285
|
|
|
193
286
|
// Close browse section and show success
|
|
194
287
|
setShowBrowseGPUs(false);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
288
|
+
setSuccessModal({
|
|
289
|
+
isOpen: true,
|
|
290
|
+
title: "Pod Created Successfully!",
|
|
291
|
+
message: `Pod "${newPod.name}" created successfully! Wait for provisioning (~2-5 min).`,
|
|
292
|
+
});
|
|
198
293
|
} catch (err) {
|
|
199
294
|
let errorMsg = "Failed to create pod";
|
|
200
295
|
|
|
@@ -210,6 +305,9 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
210
305
|
"Insufficient funds. Please add credits to your Prime Intellect wallet:\nhttps://app.primeintellect.ai/dashboard/billing";
|
|
211
306
|
} else if (errorMsg.includes("401") || errorMsg.includes("403")) {
|
|
212
307
|
errorMsg = "Authentication failed. Check your API key configuration.";
|
|
308
|
+
} else if (errorMsg.includes("503") || errorMsg.includes("not available")) {
|
|
309
|
+
errorMsg =
|
|
310
|
+
"This GPU type is currently unavailable. Please try selecting a different GPU from the list below.";
|
|
213
311
|
} else if (errorMsg.includes("data_center_id")) {
|
|
214
312
|
errorMsg =
|
|
215
313
|
"Pod configuration error: Missing data center ID. Try a different GPU or provider.";
|
|
@@ -240,27 +338,112 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
240
338
|
};
|
|
241
339
|
|
|
242
340
|
const handleConnectToPod = async (podId: string) => {
|
|
341
|
+
// Prevent double-connecting
|
|
342
|
+
if (connectingPodId === podId || connectedPodId === podId) {
|
|
343
|
+
console.log(`[CONNECT] Already connecting/connected to pod ${podId}, skipping`);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
243
347
|
setConnectingPodId(podId);
|
|
348
|
+
setConnectionState("deploying"); // Show "Deploying worker..." banner
|
|
349
|
+
setConnectionHealth("unknown"); // Reset health status during connection
|
|
244
350
|
try {
|
|
351
|
+
// Initiate connection (now returns immediately with "connecting" status)
|
|
245
352
|
const result = await connectToPod(podId);
|
|
246
|
-
|
|
353
|
+
|
|
354
|
+
if (result.status === "connecting") {
|
|
355
|
+
console.log("[CONNECT] Connection initiated, polling for completion...");
|
|
356
|
+
|
|
357
|
+
// Poll connection status until it's connected or fails
|
|
358
|
+
const maxAttempts = 30; // 30 seconds max
|
|
359
|
+
let attempts = 0;
|
|
360
|
+
let isComplete = false; // Track if polling is complete to prevent race conditions
|
|
361
|
+
|
|
362
|
+
const pollInterval = setInterval(async () => {
|
|
363
|
+
if (isComplete) return; // Skip if already completed
|
|
364
|
+
attempts++;
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const status: PodConnectionStatus = await getPodConnectionStatus();
|
|
368
|
+
|
|
369
|
+
if (status.connected && status.pod?.id === podId && !isComplete) {
|
|
370
|
+
// Successfully connected!
|
|
371
|
+
isComplete = true;
|
|
372
|
+
clearInterval(pollInterval);
|
|
373
|
+
|
|
374
|
+
setConnectedPodId(podId);
|
|
375
|
+
setKernelStatus(true);
|
|
376
|
+
setConnectionHealth("healthy");
|
|
377
|
+
setConnectingPodId(null);
|
|
378
|
+
setConnectionState("connected"); // Show "Connected!" banner
|
|
379
|
+
setDiscoveredPod(null); // Clear discovered pod state
|
|
380
|
+
|
|
381
|
+
setErrorModal({
|
|
382
|
+
isOpen: true,
|
|
383
|
+
title: "✓ Connected!",
|
|
384
|
+
message: "Successfully connected to GPU pod. You can now run code on the remote GPU.",
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Hide the connected banner after 3 seconds
|
|
388
|
+
setTimeout(() => {
|
|
389
|
+
setConnectionState(null);
|
|
390
|
+
}, 3000);
|
|
391
|
+
} else if (attempts >= maxAttempts && !isComplete) {
|
|
392
|
+
// Timeout
|
|
393
|
+
clearInterval(pollInterval);
|
|
394
|
+
setConnectionHealth("unhealthy");
|
|
395
|
+
setConnectingPodId(null);
|
|
396
|
+
setConnectionState(null); // Hide banner on failure
|
|
397
|
+
|
|
398
|
+
setErrorModal({
|
|
399
|
+
isOpen: true,
|
|
400
|
+
title: "Connection Timeout",
|
|
401
|
+
message: "Connection took too long. The pod may not be ready yet. Check the pod status and try again.",
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
// Error during polling
|
|
406
|
+
clearInterval(pollInterval);
|
|
407
|
+
setConnectionHealth("unhealthy");
|
|
408
|
+
setConnectingPodId(null);
|
|
409
|
+
setConnectionState(null); // Hide banner on error
|
|
410
|
+
|
|
411
|
+
setErrorModal({
|
|
412
|
+
isOpen: true,
|
|
413
|
+
title: "Connection Failed",
|
|
414
|
+
message: "Failed to establish connection. Please try again.",
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}, 1000); // Poll every second
|
|
418
|
+
|
|
419
|
+
} else if (result.status === "ok") {
|
|
420
|
+
// Immediate success (backwards compatible)
|
|
247
421
|
setConnectedPodId(podId);
|
|
248
|
-
setKernelStatus(true);
|
|
422
|
+
setKernelStatus(true);
|
|
423
|
+
setConnectionHealth("healthy");
|
|
424
|
+
setConnectingPodId(null);
|
|
425
|
+
setConnectionState("connected"); // Show "Connected!" banner
|
|
426
|
+
setDiscoveredPod(null); // Clear discovered pod state
|
|
427
|
+
|
|
249
428
|
setErrorModal({
|
|
250
429
|
isOpen: true,
|
|
251
430
|
title: "✓ Connected!",
|
|
252
|
-
message:
|
|
253
|
-
"Successfully connected to GPU pod. You can now run code on the remote GPU.",
|
|
431
|
+
message: "Successfully connected to GPU pod. You can now run code on the remote GPU.",
|
|
254
432
|
});
|
|
433
|
+
|
|
434
|
+
// Hide the connected banner after 3 seconds
|
|
435
|
+
setTimeout(() => {
|
|
436
|
+
setConnectionState(null);
|
|
437
|
+
}, 3000);
|
|
255
438
|
} else {
|
|
256
|
-
//
|
|
439
|
+
// Error
|
|
440
|
+
setConnectionHealth("unhealthy");
|
|
441
|
+
setConnectingPodId(null);
|
|
442
|
+
setConnectionState(null); // Hide banner on error
|
|
443
|
+
|
|
257
444
|
let errorMsg = result.message || "Connection failed";
|
|
258
445
|
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
errorMsg.includes("SSH authentication") ||
|
|
262
|
-
errorMsg.includes("SSH public key")
|
|
263
|
-
) {
|
|
446
|
+
if (errorMsg.includes("SSH authentication") || errorMsg.includes("SSH public key")) {
|
|
264
447
|
setErrorModal({
|
|
265
448
|
isOpen: true,
|
|
266
449
|
title: "SSH Key Required",
|
|
@@ -277,15 +460,16 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
277
460
|
}
|
|
278
461
|
}
|
|
279
462
|
} catch (err) {
|
|
280
|
-
const errorMsg =
|
|
281
|
-
|
|
463
|
+
const errorMsg = err instanceof Error ? err.message : "Failed to connect to pod";
|
|
464
|
+
setConnectionHealth("unhealthy");
|
|
465
|
+
setConnectingPodId(null);
|
|
466
|
+
setConnectionState(null); // Hide banner on error
|
|
467
|
+
|
|
282
468
|
setErrorModal({
|
|
283
469
|
isOpen: true,
|
|
284
470
|
title: "Connection Error",
|
|
285
|
-
message: errorMsg
|
|
471
|
+
message: `${errorMsg}\n\nThis could be due to:\n• Network connectivity issues\n• Pod may have stopped running\n• SSH tunnel creation failed`,
|
|
286
472
|
});
|
|
287
|
-
} finally {
|
|
288
|
-
setConnectingPodId(null);
|
|
289
473
|
}
|
|
290
474
|
};
|
|
291
475
|
|
|
@@ -294,7 +478,15 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
294
478
|
await disconnectFromPod();
|
|
295
479
|
setConnectedPodId(null);
|
|
296
480
|
setKernelStatus(false); // Mark kernel as not running
|
|
297
|
-
|
|
481
|
+
setConnectionHealth("unknown"); // Reset health status
|
|
482
|
+
setConnectionState(null); // Hide banner
|
|
483
|
+
|
|
484
|
+
// Show warning that pod is still running
|
|
485
|
+
setErrorModal({
|
|
486
|
+
isOpen: true,
|
|
487
|
+
title: "⚠️ Disconnected",
|
|
488
|
+
message: "Disconnected from pod. Note: The pod is still running and incurring costs. You can reconnect or terminate it below.",
|
|
489
|
+
});
|
|
298
490
|
} catch (err) {
|
|
299
491
|
const errorMsg =
|
|
300
492
|
err instanceof Error ? err.message : "Failed to disconnect";
|
|
@@ -303,29 +495,54 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
303
495
|
};
|
|
304
496
|
|
|
305
497
|
const handleDeletePod = async (podId: string, podName: string) => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
498
|
+
// Show confirmation modal instead of native confirm dialog
|
|
499
|
+
setConfirmModal({
|
|
500
|
+
isOpen: true,
|
|
501
|
+
title: "Terminate Pod?",
|
|
502
|
+
message: `Are you sure you want to terminate pod "${podName}"?\n\nThis will stop the pod and you will no longer be charged for it.`,
|
|
503
|
+
onConfirm: async () => {
|
|
504
|
+
setDeletingPodId(podId);
|
|
505
|
+
try {
|
|
506
|
+
// Disconnect if this is the connected pod
|
|
507
|
+
if (connectedPodId === podId) {
|
|
508
|
+
await disconnectFromPod();
|
|
509
|
+
setConnectedPodId(null);
|
|
510
|
+
setKernelStatus(false);
|
|
511
|
+
setConnectionHealth("unknown");
|
|
512
|
+
}
|
|
309
513
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
setConnectedPodId(null);
|
|
316
|
-
setKernelStatus(false);
|
|
317
|
-
}
|
|
514
|
+
// Clear connection state if deleting the connecting pod
|
|
515
|
+
if (connectingPodId === podId) {
|
|
516
|
+
setConnectingPodId(null);
|
|
517
|
+
setConnectionState(null);
|
|
518
|
+
}
|
|
318
519
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
520
|
+
await deleteGpuPod(podId);
|
|
521
|
+
|
|
522
|
+
// Clear discovered pod if it was the one we just deleted
|
|
523
|
+
if (discoveredPod && discoveredPod.id === podId) {
|
|
524
|
+
setDiscoveredPod(null);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
setSuccessModal({
|
|
528
|
+
isOpen: true,
|
|
529
|
+
title: "Pod Terminated",
|
|
530
|
+
message: `Pod "${podName}" terminated successfully.`,
|
|
531
|
+
});
|
|
532
|
+
await loadGPUPods();
|
|
533
|
+
} catch (err) {
|
|
534
|
+
const errorMsg =
|
|
535
|
+
err instanceof Error ? err.message : "Failed to terminate pod";
|
|
536
|
+
setErrorModal({
|
|
537
|
+
isOpen: true,
|
|
538
|
+
title: "Termination Failed",
|
|
539
|
+
message: `Failed to terminate pod: ${errorMsg}`,
|
|
540
|
+
});
|
|
541
|
+
} finally {
|
|
542
|
+
setDeletingPodId(null);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
});
|
|
329
546
|
};
|
|
330
547
|
|
|
331
548
|
const handleConnectToPrimeIntellect = () => {
|
|
@@ -355,6 +572,19 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
355
572
|
}
|
|
356
573
|
};
|
|
357
574
|
|
|
575
|
+
// Filter GPUs based on search query (memoized for performance)
|
|
576
|
+
const filteredGPUs = useMemo(() => {
|
|
577
|
+
if (!searchQuery.trim()) {
|
|
578
|
+
return availableGPUs;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const query = searchQuery.toLowerCase();
|
|
582
|
+
return availableGPUs.filter((gpu) => {
|
|
583
|
+
const gpuType = gpu.gpuType.toLowerCase();
|
|
584
|
+
return gpuType.includes(query);
|
|
585
|
+
});
|
|
586
|
+
}, [searchQuery, availableGPUs]);
|
|
587
|
+
|
|
358
588
|
return (
|
|
359
589
|
<>
|
|
360
590
|
<ErrorModal
|
|
@@ -365,11 +595,27 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
365
595
|
actionLabel={errorModal.actionLabel}
|
|
366
596
|
actionUrl={errorModal.actionUrl}
|
|
367
597
|
/>
|
|
598
|
+
<SuccessModal
|
|
599
|
+
isOpen={successModal.isOpen}
|
|
600
|
+
onClose={() => setSuccessModal({ ...successModal, isOpen: false })}
|
|
601
|
+
title={successModal.title}
|
|
602
|
+
message={successModal.message}
|
|
603
|
+
/>
|
|
604
|
+
<ConfirmModal
|
|
605
|
+
isOpen={confirmModal.isOpen}
|
|
606
|
+
onClose={() => setConfirmModal({ ...confirmModal, isOpen: false })}
|
|
607
|
+
onConfirm={confirmModal.onConfirm}
|
|
608
|
+
title={confirmModal.title}
|
|
609
|
+
message={confirmModal.message}
|
|
610
|
+
confirmLabel="Terminate"
|
|
611
|
+
cancelLabel="Cancel"
|
|
612
|
+
isDangerous={true}
|
|
613
|
+
/>
|
|
368
614
|
<div className="runtime-popup">
|
|
369
615
|
{/* Kernel Status Section */}
|
|
370
616
|
<section
|
|
371
617
|
className="runtime-section"
|
|
372
|
-
style={{ padding: "
|
|
618
|
+
style={{ padding: "16px 20px" }}
|
|
373
619
|
>
|
|
374
620
|
<div
|
|
375
621
|
style={{
|
|
@@ -378,57 +624,80 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
378
624
|
alignItems: "center",
|
|
379
625
|
}}
|
|
380
626
|
>
|
|
381
|
-
<
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
? "kernel-status-active"
|
|
387
|
-
: "kernel-status-inactive"
|
|
388
|
-
}
|
|
389
|
-
>
|
|
390
|
-
{kernelStatus ? "running" : "not running"}
|
|
391
|
-
</span>
|
|
392
|
-
</h3>
|
|
393
|
-
<button
|
|
394
|
-
className="runtime-btn runtime-btn-secondary"
|
|
395
|
-
style={{ fontSize: "11px", padding: "3px 8px" }}
|
|
396
|
-
>
|
|
397
|
-
Stop kernel
|
|
398
|
-
</button>
|
|
627
|
+
<div>
|
|
628
|
+
<div style={{ fontSize: "11px", color: "var(--text-secondary)", marginBottom: "4px" }}>
|
|
629
|
+
The kernel is currently: {kernelStatus ? "Running" : "Stopped"}
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
399
632
|
</div>
|
|
400
633
|
</section>
|
|
401
634
|
|
|
635
|
+
{/* Divider */}
|
|
636
|
+
<div style={{
|
|
637
|
+
height: "1px",
|
|
638
|
+
backgroundColor: "rgba(128, 128, 128, 0.2)",
|
|
639
|
+
margin: "0 16px"
|
|
640
|
+
}} />
|
|
641
|
+
|
|
402
642
|
{/* Compute Profile Section */}
|
|
403
|
-
<section className="runtime-section" style={{ padding: "
|
|
643
|
+
<section className="runtime-section" style={{ padding: "12px 16px" }}>
|
|
404
644
|
<div
|
|
405
645
|
style={{
|
|
406
646
|
display: "flex",
|
|
407
647
|
justifyContent: "space-between",
|
|
408
648
|
alignItems: "center",
|
|
409
|
-
marginBottom: "3px",
|
|
410
649
|
}}
|
|
411
650
|
>
|
|
412
|
-
<h3 className="runtime-section-title" style={{ fontSize: "12px" }}>
|
|
413
|
-
Compute
|
|
651
|
+
<h3 className="runtime-section-title" style={{ fontSize: "12px", fontWeight: 500 }}>
|
|
652
|
+
Compute Profile
|
|
414
653
|
</h3>
|
|
415
|
-
<span className="runtime-cost" style={{ fontSize: "
|
|
416
|
-
|
|
654
|
+
<span className="runtime-cost" style={{ fontSize: "12px", fontWeight: 500 }}>
|
|
655
|
+
{(() => {
|
|
656
|
+
const runningPods = gpuPods.filter(p => p.status === "running");
|
|
657
|
+
const totalCost = runningPods.reduce((sum, pod) => sum + pod.costPerHour, 0);
|
|
658
|
+
return `$${totalCost.toFixed(2)} / hour`;
|
|
659
|
+
})()}
|
|
417
660
|
</span>
|
|
418
661
|
</div>
|
|
662
|
+
</section>
|
|
663
|
+
|
|
664
|
+
{/* Divider */}
|
|
665
|
+
<div style={{
|
|
666
|
+
height: "1px",
|
|
667
|
+
backgroundColor: "rgba(128, 128, 128, 0.2)",
|
|
668
|
+
margin: "0 16px"
|
|
669
|
+
}} />
|
|
419
670
|
|
|
420
671
|
{/* GPU Pods Section */}
|
|
421
|
-
<
|
|
672
|
+
<section className="runtime-section" style={{ padding: "12px 16px" }}>
|
|
422
673
|
<div
|
|
423
|
-
|
|
424
|
-
|
|
674
|
+
style={{
|
|
675
|
+
display: "flex",
|
|
676
|
+
justifyContent: "space-between",
|
|
677
|
+
alignItems: "center",
|
|
678
|
+
marginBottom: "12px"
|
|
679
|
+
}}
|
|
425
680
|
>
|
|
426
|
-
<
|
|
427
|
-
className="runtime-subsection-title"
|
|
428
|
-
style={{ fontSize: "11px" }}
|
|
429
|
-
>
|
|
681
|
+
<h3 className="runtime-section-title" style={{ fontSize: "12px", fontWeight: 500 }}>
|
|
430
682
|
Remote GPU Pods
|
|
431
|
-
</
|
|
683
|
+
</h3>
|
|
684
|
+
{apiConfigured && (
|
|
685
|
+
<button
|
|
686
|
+
className="runtime-btn runtime-btn-secondary"
|
|
687
|
+
onClick={handleConnectToPrimeIntellect}
|
|
688
|
+
style={{
|
|
689
|
+
fontSize: "11px",
|
|
690
|
+
padding: "6px 12px",
|
|
691
|
+
backgroundColor: "#000",
|
|
692
|
+
color: "white",
|
|
693
|
+
border: "none",
|
|
694
|
+
borderRadius: "8px",
|
|
695
|
+
cursor: "pointer"
|
|
696
|
+
}}
|
|
697
|
+
>
|
|
698
|
+
Manage
|
|
699
|
+
</button>
|
|
700
|
+
)}
|
|
432
701
|
</div>
|
|
433
702
|
|
|
434
703
|
{apiConfigured === false ? (
|
|
@@ -451,8 +720,8 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
451
720
|
onKeyPress={(e) => e.key === "Enter" && handleSaveApiKey()}
|
|
452
721
|
style={{
|
|
453
722
|
width: "100%",
|
|
454
|
-
padding: "
|
|
455
|
-
borderRadius: "
|
|
723
|
+
padding: "6px 12px",
|
|
724
|
+
borderRadius: "8px",
|
|
456
725
|
border: "1px solid var(--border-color)",
|
|
457
726
|
backgroundColor: "var(--background)",
|
|
458
727
|
color: "var(--text)",
|
|
@@ -477,14 +746,31 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
477
746
|
className="runtime-btn runtime-btn-primary"
|
|
478
747
|
onClick={handleSaveApiKey}
|
|
479
748
|
disabled={saving}
|
|
480
|
-
style={{
|
|
749
|
+
style={{
|
|
750
|
+
flex: 1,
|
|
751
|
+
fontSize: "11px",
|
|
752
|
+
padding: "6px 12px",
|
|
753
|
+
backgroundColor: "#000",
|
|
754
|
+
color: "white",
|
|
755
|
+
border: "none",
|
|
756
|
+
borderRadius: "8px",
|
|
757
|
+
cursor: "pointer"
|
|
758
|
+
}}
|
|
481
759
|
>
|
|
482
760
|
{saving ? "Saving..." : "Save"}
|
|
483
761
|
</button>
|
|
484
762
|
<button
|
|
485
763
|
className="runtime-btn runtime-btn-secondary"
|
|
486
764
|
onClick={handleConnectToPrimeIntellect}
|
|
487
|
-
style={{
|
|
765
|
+
style={{
|
|
766
|
+
fontSize: "11px",
|
|
767
|
+
padding: "6px 12px",
|
|
768
|
+
backgroundColor: "#000",
|
|
769
|
+
color: "white",
|
|
770
|
+
border: "none",
|
|
771
|
+
borderRadius: "8px",
|
|
772
|
+
cursor: "pointer"
|
|
773
|
+
}}
|
|
488
774
|
>
|
|
489
775
|
<ExternalLink size={10} style={{ marginRight: "3px" }} />
|
|
490
776
|
Get Key
|
|
@@ -492,185 +778,222 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
492
778
|
</div>
|
|
493
779
|
</div>
|
|
494
780
|
) : loading || apiConfigured === null ? (
|
|
495
|
-
<div
|
|
496
|
-
|
|
497
|
-
Loading...
|
|
498
|
-
</p>
|
|
781
|
+
<div style={{ padding: "8px 0", color: "var(--text-secondary)", fontSize: "11px" }}>
|
|
782
|
+
Loading...
|
|
499
783
|
</div>
|
|
500
|
-
) : gpuPods.length === 0 ? (
|
|
501
|
-
<div
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
style={{
|
|
521
|
-
>
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
784
|
+
) : gpuPods.filter(p => p.status === "running").length === 0 && !discoveredPod ? (
|
|
785
|
+
<div style={{ padding: "8px 0", color: "var(--text-secondary)", fontSize: "11px" }}>
|
|
786
|
+
Currently not connected to any.
|
|
787
|
+
</div>
|
|
788
|
+
) : !connectedPodId && discoveredPod ? (
|
|
789
|
+
<div style={{ padding: "8px 0" }}>
|
|
790
|
+
<div style={{
|
|
791
|
+
backgroundColor: "#fff3cd",
|
|
792
|
+
border: "1px solid #ffc107",
|
|
793
|
+
borderRadius: "8px",
|
|
794
|
+
padding: "12px",
|
|
795
|
+
marginBottom: "8px"
|
|
796
|
+
}}>
|
|
797
|
+
<div style={{ fontSize: "11px", fontWeight: 600, color: "#856404", marginBottom: "4px" }}>
|
|
798
|
+
⚠️ Running Pod Detected
|
|
799
|
+
</div>
|
|
800
|
+
<div style={{ fontSize: "10px", color: "#856404", marginBottom: "8px" }}>
|
|
801
|
+
Found a running pod but not connected (backend may have restarted). This pod is still costing money!
|
|
802
|
+
</div>
|
|
803
|
+
<div style={{ fontSize: "10px", marginBottom: "8px" }}>
|
|
804
|
+
<span style={{ fontWeight: 500 }}>{discoveredPod.name}</span> • {discoveredPod.gpuType} • ${discoveredPod.costPerHour.toFixed(2)}/hour
|
|
805
|
+
</div>
|
|
806
|
+
<div style={{ display: "flex", gap: "6px" }}>
|
|
807
|
+
<button
|
|
808
|
+
className="runtime-btn runtime-btn-secondary"
|
|
809
|
+
onClick={() => handleConnectToPod(discoveredPod.id)}
|
|
810
|
+
style={{
|
|
811
|
+
flex: 1,
|
|
812
|
+
fontSize: "10px",
|
|
813
|
+
padding: "6px 12px",
|
|
814
|
+
backgroundColor: "#ffc107",
|
|
815
|
+
color: "#000",
|
|
816
|
+
border: "none",
|
|
817
|
+
borderRadius: "8px",
|
|
818
|
+
cursor: "pointer",
|
|
819
|
+
fontWeight: 600
|
|
820
|
+
}}
|
|
821
|
+
>
|
|
822
|
+
Reconnect
|
|
823
|
+
</button>
|
|
824
|
+
<button
|
|
825
|
+
className="runtime-btn runtime-btn-secondary"
|
|
826
|
+
onClick={() => handleDeletePod(discoveredPod.id, discoveredPod.name)}
|
|
827
|
+
disabled={deletingPodId === discoveredPod.id}
|
|
828
|
+
style={{
|
|
829
|
+
fontSize: "10px",
|
|
830
|
+
padding: "6px 12px",
|
|
831
|
+
backgroundColor: "#dc2626",
|
|
832
|
+
color: "white",
|
|
833
|
+
border: "none",
|
|
834
|
+
borderRadius: "8px",
|
|
835
|
+
cursor: "pointer"
|
|
836
|
+
}}
|
|
837
|
+
>
|
|
838
|
+
{deletingPodId === discoveredPod.id ? "..." : "Terminate"}
|
|
839
|
+
</button>
|
|
840
|
+
</div>
|
|
533
841
|
</div>
|
|
534
842
|
</div>
|
|
535
843
|
) : (
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
844
|
+
gpuPods
|
|
845
|
+
.filter((pod) => pod.status === "running")
|
|
846
|
+
.map((pod) => {
|
|
847
|
+
const isConnected = pod.id === connectedPodId;
|
|
848
|
+
return (
|
|
849
|
+
<div key={pod.id} style={{ padding: "8px 0" }}>
|
|
850
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "8px" }}>
|
|
851
|
+
<div>
|
|
852
|
+
<div style={{ fontSize: "11px", marginBottom: "4px" }}>
|
|
853
|
+
<span style={{ fontWeight: 500 }}>{pod.name}</span>
|
|
854
|
+
{isConnected && (
|
|
855
|
+
<span style={{
|
|
856
|
+
marginLeft: "6px",
|
|
857
|
+
fontSize: "9px",
|
|
858
|
+
backgroundColor: "#10b981",
|
|
859
|
+
color: "white",
|
|
860
|
+
padding: "2px 6px",
|
|
861
|
+
borderRadius: "4px"
|
|
862
|
+
}}>
|
|
863
|
+
Connected
|
|
864
|
+
</span>
|
|
865
|
+
)}
|
|
866
|
+
</div>
|
|
867
|
+
<div style={{ fontSize: "10px", color: "var(--text-secondary)" }}>
|
|
868
|
+
{pod.gpuType} • ${pod.costPerHour.toFixed(2)}/hour
|
|
869
|
+
</div>
|
|
560
870
|
</div>
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
871
|
+
<div style={{ display: "flex", gap: "6px" }}>
|
|
872
|
+
{isConnected ? (
|
|
873
|
+
<>
|
|
874
|
+
<button
|
|
875
|
+
className="runtime-btn runtime-btn-secondary"
|
|
876
|
+
onClick={handleDisconnect}
|
|
877
|
+
style={{
|
|
878
|
+
fontSize: "10px",
|
|
879
|
+
padding: "6px 12px",
|
|
880
|
+
backgroundColor: "#000",
|
|
881
|
+
color: "white",
|
|
882
|
+
border: "none",
|
|
883
|
+
borderRadius: "8px",
|
|
884
|
+
cursor: "pointer"
|
|
885
|
+
}}
|
|
886
|
+
>
|
|
887
|
+
Disconnect
|
|
888
|
+
</button>
|
|
889
|
+
<button
|
|
890
|
+
className="runtime-btn runtime-btn-secondary"
|
|
891
|
+
onClick={() => handleDeletePod(pod.id, pod.name)}
|
|
892
|
+
disabled={deletingPodId === pod.id}
|
|
893
|
+
style={{
|
|
894
|
+
fontSize: "10px",
|
|
895
|
+
padding: "6px 12px",
|
|
896
|
+
backgroundColor: "#dc2626",
|
|
897
|
+
color: "white",
|
|
898
|
+
border: "none",
|
|
899
|
+
borderRadius: "8px",
|
|
900
|
+
cursor: "pointer"
|
|
901
|
+
}}
|
|
902
|
+
>
|
|
903
|
+
{deletingPodId === pod.id ? "..." : "Terminate"}
|
|
904
|
+
</button>
|
|
905
|
+
</>
|
|
576
906
|
) : (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
907
|
+
<>
|
|
908
|
+
<button
|
|
909
|
+
className="runtime-btn runtime-btn-secondary"
|
|
910
|
+
onClick={() => handleConnectToPod(pod.id)}
|
|
911
|
+
disabled={connectingPodId === pod.id}
|
|
912
|
+
style={{
|
|
913
|
+
fontSize: "10px",
|
|
914
|
+
padding: "6px 12px",
|
|
915
|
+
backgroundColor: "#10b981",
|
|
916
|
+
color: "white",
|
|
917
|
+
border: "none",
|
|
918
|
+
borderRadius: "8px",
|
|
919
|
+
cursor: "pointer"
|
|
920
|
+
}}
|
|
921
|
+
>
|
|
922
|
+
{connectingPodId === pod.id ? "Connecting..." : "Connect"}
|
|
923
|
+
</button>
|
|
924
|
+
<button
|
|
925
|
+
className="runtime-btn runtime-btn-secondary"
|
|
926
|
+
onClick={() => handleDeletePod(pod.id, pod.name)}
|
|
927
|
+
disabled={deletingPodId === pod.id}
|
|
928
|
+
style={{
|
|
929
|
+
fontSize: "10px",
|
|
930
|
+
padding: "6px 12px",
|
|
931
|
+
backgroundColor: "#dc2626",
|
|
932
|
+
color: "white",
|
|
933
|
+
border: "none",
|
|
934
|
+
borderRadius: "8px",
|
|
935
|
+
cursor: "pointer"
|
|
936
|
+
}}
|
|
937
|
+
>
|
|
938
|
+
{deletingPodId === pod.id ? "..." : "Terminate"}
|
|
939
|
+
</button>
|
|
940
|
+
</>
|
|
941
|
+
)}
|
|
942
|
+
</div>
|
|
612
943
|
</div>
|
|
613
944
|
</div>
|
|
614
|
-
)
|
|
615
|
-
|
|
616
|
-
<div style={{ display: "flex", gap: "4px" }}>
|
|
617
|
-
<button
|
|
618
|
-
className="runtime-btn runtime-btn-link"
|
|
619
|
-
onClick={() => loadGPUPods()}
|
|
620
|
-
style={{ fontSize: "12px", padding: "6px 8px", flex: 1 }}
|
|
621
|
-
>
|
|
622
|
-
Refresh
|
|
623
|
-
</button>
|
|
624
|
-
<button
|
|
625
|
-
className="runtime-btn runtime-btn-link"
|
|
626
|
-
onClick={() => {
|
|
627
|
-
setShowBrowseGPUs(!showBrowseGPUs);
|
|
628
|
-
if (!showBrowseGPUs && availableGPUs.length === 0) {
|
|
629
|
-
loadAvailableGPUs();
|
|
630
|
-
}
|
|
631
|
-
}}
|
|
632
|
-
style={{ fontSize: "12px", padding: "6px 8px", flex: 1 }}
|
|
633
|
-
>
|
|
634
|
-
<Plus size={12} style={{ marginRight: "4px" }} />
|
|
635
|
-
Browse GPUs
|
|
636
|
-
</button>
|
|
637
|
-
</div>
|
|
638
|
-
</>
|
|
945
|
+
);
|
|
946
|
+
})
|
|
639
947
|
)}
|
|
640
|
-
</
|
|
948
|
+
</section>
|
|
641
949
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
>
|
|
649
|
-
<h4
|
|
650
|
-
className="runtime-subsection-title"
|
|
651
|
-
style={{ fontSize: "11px" }}
|
|
652
|
-
>
|
|
653
|
-
<Filter size={10} style={{ marginRight: "2px" }} />
|
|
654
|
-
Browse GPUs
|
|
655
|
-
</h4>
|
|
656
|
-
</div>
|
|
950
|
+
{/* Divider */}
|
|
951
|
+
<div style={{
|
|
952
|
+
height: "1px",
|
|
953
|
+
backgroundColor: "rgba(128, 128, 128, 0.2)",
|
|
954
|
+
margin: "0 16px"
|
|
955
|
+
}} />
|
|
657
956
|
|
|
658
|
-
|
|
957
|
+
{/* Browse Available GPUs Section */}
|
|
958
|
+
{apiConfigured && (
|
|
959
|
+
<section className="runtime-section" style={{ padding: "12px 16px" }}>
|
|
960
|
+
{/* Search and Filter Bar */}
|
|
659
961
|
<div
|
|
660
962
|
style={{
|
|
661
|
-
marginBottom: "
|
|
963
|
+
marginBottom: "20px",
|
|
662
964
|
display: "flex",
|
|
663
|
-
gap: "
|
|
965
|
+
gap: "8px",
|
|
664
966
|
alignItems: "center",
|
|
665
967
|
}}
|
|
666
968
|
>
|
|
969
|
+
<input
|
|
970
|
+
type="text"
|
|
971
|
+
placeholder="Search GPU (e.g., H100, A100, RTX 4090)"
|
|
972
|
+
value={searchQuery}
|
|
973
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
974
|
+
style={{
|
|
975
|
+
flex: 1,
|
|
976
|
+
padding: "6px 12px",
|
|
977
|
+
fontSize: "11px",
|
|
978
|
+
border: "1px solid #d1d5db",
|
|
979
|
+
borderRadius: "8px",
|
|
980
|
+
backgroundColor: "var(--background)",
|
|
981
|
+
color: "var(--text)",
|
|
982
|
+
outline: "none",
|
|
983
|
+
}}
|
|
984
|
+
/>
|
|
667
985
|
<button
|
|
668
986
|
className="runtime-btn runtime-btn-secondary"
|
|
669
987
|
onClick={() => setShowFilterPopup(!showFilterPopup)}
|
|
670
988
|
style={{
|
|
671
|
-
padding: "
|
|
989
|
+
padding: "6px 12px",
|
|
672
990
|
fontSize: "11px",
|
|
673
991
|
position: "relative",
|
|
992
|
+
backgroundColor: "#000",
|
|
993
|
+
color: "white",
|
|
994
|
+
border: "none",
|
|
995
|
+
borderRadius: "8px",
|
|
996
|
+
cursor: "pointer"
|
|
674
997
|
}}
|
|
675
998
|
>
|
|
676
999
|
<Filter size={10} style={{ marginRight: "3px" }} />
|
|
@@ -692,19 +1015,6 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
692
1015
|
/>
|
|
693
1016
|
)}
|
|
694
1017
|
</button>
|
|
695
|
-
<button
|
|
696
|
-
className="runtime-btn runtime-btn-primary"
|
|
697
|
-
onClick={loadAvailableGPUs}
|
|
698
|
-
disabled={loadingAvailability}
|
|
699
|
-
style={{
|
|
700
|
-
flex: 1,
|
|
701
|
-
padding: "4px 8px",
|
|
702
|
-
fontSize: "11px",
|
|
703
|
-
}}
|
|
704
|
-
>
|
|
705
|
-
<Search size={10} style={{ marginRight: "3px" }} />
|
|
706
|
-
{loadingAvailability ? "Searching..." : "Search"}
|
|
707
|
-
</button>
|
|
708
1018
|
</div>
|
|
709
1019
|
|
|
710
1020
|
{/* Filter Popup */}
|
|
@@ -718,40 +1028,39 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
718
1028
|
|
|
719
1029
|
{/* Results */}
|
|
720
1030
|
{loadingAvailability ? (
|
|
721
|
-
<div
|
|
722
|
-
|
|
723
|
-
style={{ color: "var(--text-secondary)", fontSize: "10px" }}
|
|
724
|
-
>
|
|
725
|
-
Loading...
|
|
726
|
-
</p>
|
|
1031
|
+
<div style={{ padding: "16px 0", textAlign: "center", color: "var(--text-secondary)", fontSize: "11px" }}>
|
|
1032
|
+
Loading...
|
|
727
1033
|
</div>
|
|
728
1034
|
) : availableGPUs.length === 0 ? (
|
|
729
|
-
<div
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1035
|
+
<div style={{ padding: "16px 0", textAlign: "center", color: "var(--text-secondary)", fontSize: "11px" }}>
|
|
1036
|
+
Use filters to find available GPUs
|
|
1037
|
+
</div>
|
|
1038
|
+
) : filteredGPUs.length === 0 ? (
|
|
1039
|
+
<div style={{ padding: "16px 0", textAlign: "center", color: "var(--text-secondary)", fontSize: "11px" }}>
|
|
1040
|
+
No GPUs match "{searchQuery}"
|
|
735
1041
|
</div>
|
|
736
1042
|
) : (
|
|
737
|
-
<div style={{ maxHeight: "
|
|
738
|
-
{
|
|
739
|
-
<
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1043
|
+
<div style={{ maxHeight: "calc(100vh - 400px)", overflowY: "auto", paddingRight: "12px" }}>
|
|
1044
|
+
{filteredGPUs.map((gpu, index) => (
|
|
1045
|
+
<React.Fragment key={`${gpu.cloudId}-${index}`}>
|
|
1046
|
+
{index > 0 && (
|
|
1047
|
+
<div style={{
|
|
1048
|
+
height: "1px",
|
|
1049
|
+
backgroundColor: "rgba(128, 128, 128, 0.15)",
|
|
1050
|
+
margin: "8px 0"
|
|
1051
|
+
}} />
|
|
1052
|
+
)}
|
|
1053
|
+
<div
|
|
1054
|
+
style={{
|
|
1055
|
+
padding: "8px 0",
|
|
1056
|
+
}}
|
|
1057
|
+
>
|
|
749
1058
|
<div
|
|
750
1059
|
style={{
|
|
751
1060
|
display: "flex",
|
|
752
1061
|
justifyContent: "space-between",
|
|
753
1062
|
alignItems: "flex-start",
|
|
754
|
-
marginBottom: "
|
|
1063
|
+
marginBottom: "8px",
|
|
755
1064
|
}}
|
|
756
1065
|
>
|
|
757
1066
|
<div>
|
|
@@ -773,30 +1082,54 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
773
1082
|
{gpu.provider} - {gpu.socket} - {gpu.gpuMemory}GB
|
|
774
1083
|
</div>
|
|
775
1084
|
</div>
|
|
776
|
-
<div style={{ textAlign: "right" }}>
|
|
777
|
-
<div
|
|
778
|
-
style={{
|
|
779
|
-
fontWeight: 600,
|
|
780
|
-
fontSize: "11px",
|
|
781
|
-
color: "var(--accent)",
|
|
782
|
-
}}
|
|
783
|
-
>
|
|
784
|
-
${gpu.prices?.onDemand?.toFixed(2) || "N/A"}/hr
|
|
785
|
-
</div>
|
|
786
|
-
{gpu.stockStatus && (
|
|
1085
|
+
<div style={{ textAlign: "right", display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "6px" }}>
|
|
1086
|
+
<div>
|
|
787
1087
|
<div
|
|
788
1088
|
style={{
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
? "var(--success)"
|
|
793
|
-
: "var(--text-secondary)",
|
|
794
|
-
marginTop: "1px",
|
|
1089
|
+
fontWeight: 600,
|
|
1090
|
+
fontSize: "11px",
|
|
1091
|
+
color: "var(--accent)",
|
|
795
1092
|
}}
|
|
796
1093
|
>
|
|
797
|
-
{gpu.
|
|
1094
|
+
${gpu.prices?.onDemand?.toFixed(2) || "N/A"}/hr
|
|
798
1095
|
</div>
|
|
799
|
-
|
|
1096
|
+
{gpu.stockStatus && (
|
|
1097
|
+
<div
|
|
1098
|
+
style={{
|
|
1099
|
+
fontSize: "9px",
|
|
1100
|
+
color:
|
|
1101
|
+
gpu.stockStatus === "Available"
|
|
1102
|
+
? "var(--success)"
|
|
1103
|
+
: "var(--text-secondary)",
|
|
1104
|
+
marginTop: "1px",
|
|
1105
|
+
}}
|
|
1106
|
+
>
|
|
1107
|
+
{gpu.stockStatus}
|
|
1108
|
+
</div>
|
|
1109
|
+
)}
|
|
1110
|
+
</div>
|
|
1111
|
+
<button
|
|
1112
|
+
className="runtime-btn runtime-btn-sm runtime-btn-primary"
|
|
1113
|
+
onClick={(e) => {
|
|
1114
|
+
e.stopPropagation();
|
|
1115
|
+
createPodFromGpu(gpu);
|
|
1116
|
+
}}
|
|
1117
|
+
disabled={creatingPodId === gpu.cloudId}
|
|
1118
|
+
style={{
|
|
1119
|
+
fontSize: "10px",
|
|
1120
|
+
padding: "6px 16px",
|
|
1121
|
+
whiteSpace: "nowrap",
|
|
1122
|
+
backgroundColor: "#000",
|
|
1123
|
+
color: "white",
|
|
1124
|
+
border: "none",
|
|
1125
|
+
borderRadius: "8px",
|
|
1126
|
+
cursor: "pointer"
|
|
1127
|
+
}}
|
|
1128
|
+
>
|
|
1129
|
+
{creatingPodId === gpu.cloudId
|
|
1130
|
+
? "Selecting..."
|
|
1131
|
+
: "Select"}
|
|
1132
|
+
</button>
|
|
800
1133
|
</div>
|
|
801
1134
|
</div>
|
|
802
1135
|
<div
|
|
@@ -806,71 +1139,47 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
806
1139
|
fontSize: "9px",
|
|
807
1140
|
color: "var(--text-secondary)",
|
|
808
1141
|
alignItems: "center",
|
|
809
|
-
justifyContent: "space-between",
|
|
810
1142
|
}}
|
|
811
1143
|
>
|
|
812
|
-
|
|
813
|
-
style={{
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
fontSize: "9px",
|
|
844
|
-
}}
|
|
845
|
-
>
|
|
846
|
-
{gpu.security === "secure_cloud"
|
|
847
|
-
? "Secure"
|
|
848
|
-
: "Community"}
|
|
849
|
-
</span>
|
|
850
|
-
)}
|
|
851
|
-
</div>
|
|
852
|
-
<button
|
|
853
|
-
className="runtime-btn runtime-btn-sm runtime-btn-primary"
|
|
854
|
-
onClick={() => createPodFromGpu(gpu)}
|
|
855
|
-
disabled={creatingPodId === gpu.cloudId}
|
|
856
|
-
style={{
|
|
857
|
-
fontSize: "10px",
|
|
858
|
-
padding: "3px 6px",
|
|
859
|
-
whiteSpace: "nowrap",
|
|
860
|
-
}}
|
|
861
|
-
>
|
|
862
|
-
{creatingPodId === gpu.cloudId
|
|
863
|
-
? "Creating..."
|
|
864
|
-
: "Create"}
|
|
865
|
-
</button>
|
|
1144
|
+
{gpu.region && (
|
|
1145
|
+
<span style={{ marginRight: "8px" }}>
|
|
1146
|
+
{gpu.region}
|
|
1147
|
+
</span>
|
|
1148
|
+
)}
|
|
1149
|
+
{gpu.dataCenter && (
|
|
1150
|
+
<span style={{ marginRight: "8px" }}>
|
|
1151
|
+
{gpu.dataCenter}
|
|
1152
|
+
</span>
|
|
1153
|
+
)}
|
|
1154
|
+
{gpu.security && (
|
|
1155
|
+
<span
|
|
1156
|
+
style={{
|
|
1157
|
+
backgroundColor:
|
|
1158
|
+
gpu.security === "secure_cloud"
|
|
1159
|
+
? "var(--success-bg)"
|
|
1160
|
+
: "var(--info-bg)",
|
|
1161
|
+
color:
|
|
1162
|
+
gpu.security === "secure_cloud"
|
|
1163
|
+
? "var(--success)"
|
|
1164
|
+
: "var(--info)",
|
|
1165
|
+
padding: "1px 4px",
|
|
1166
|
+
borderRadius: "2px",
|
|
1167
|
+
fontSize: "9px",
|
|
1168
|
+
}}
|
|
1169
|
+
>
|
|
1170
|
+
{gpu.security === "secure_cloud"
|
|
1171
|
+
? "Secure"
|
|
1172
|
+
: "Community"}
|
|
1173
|
+
</span>
|
|
1174
|
+
)}
|
|
866
1175
|
</div>
|
|
867
1176
|
</div>
|
|
1177
|
+
</React.Fragment>
|
|
868
1178
|
))}
|
|
869
1179
|
</div>
|
|
870
1180
|
)}
|
|
871
|
-
</
|
|
1181
|
+
</section>
|
|
872
1182
|
)}
|
|
873
|
-
</section>
|
|
874
1183
|
</div>
|
|
875
1184
|
</>
|
|
876
1185
|
);
|