diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index e32a9acd..890513a7 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@monaco-editor/react": "^4.7.0",
+ "@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-scroll-area": "^1.2.10",
@@ -19,8 +20,10 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.575.0",
+ "next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^3.4.19",
"tailwindcss-animate": "^1.0.7"
@@ -1104,6 +1107,52 @@
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT"
},
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz",
+ "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dialog": "1.1.15",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-collection": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
@@ -3810,6 +3859,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -4449,6 +4508,16 @@
"node": ">=8"
}
},
+ "node_modules/sonner": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index ea1b3300..764961b7 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,6 +11,7 @@
},
"dependencies": {
"@monaco-editor/react": "^4.7.0",
+ "@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-scroll-area": "^1.2.10",
@@ -21,8 +22,10 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.575.0",
+ "next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^3.4.19",
"tailwindcss-animate": "^1.0.7"
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index c91d7ef9..22ccc167 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -33,10 +33,13 @@ function AppContent() {
return ;
}
+import { Toaster } from 'sonner';
+
function App() {
return (
+
);
}
diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx
index 55d0fcb1..d9712c38 100644
--- a/frontend/src/components/EditorLayout.tsx
+++ b/frontend/src/components/EditorLayout.tsx
@@ -7,13 +7,16 @@ import BashExecModal from './BashExecModal';
import MaintenanceModal from './MaintenanceModal';
import { Button } from './ui/button';
import { Input } from './ui/input';
-import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, DialogTrigger } from './ui/dialog';
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogTrigger } from './ui/dialog';
+import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from './ui/alert-dialog';
import { Tabs, TabsList, TabsTrigger } from './ui/tabs';
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
import { Badge } from './ui/badge';
import { Plus, Trash2, Play, Square, Save, Terminal, Sun, Moon, RotateCw, CloudDownload, Pencil, X, Search, Home, LogOut, Brush } from 'lucide-react';
import { useAuth } from '@/context/AuthContext';
import { apiFetch } from '@/lib/api';
+import { toast } from 'sonner';
+import { Label } from './ui/label';
interface ContainerInfo {
Id: string;
@@ -209,10 +212,10 @@ export default function EditorLayout() {
setOriginalEnvContent(envContent);
}
setIsEditing(false);
- alert('File saved successfully!');
+ toast.success('File saved successfully!');
} catch (error) {
console.error('Failed to save file:', error);
- alert(`Failed to save file: ${(error as Error).message}`);
+ toast.error(`Failed to save file: ${(error as Error).message}`);
}
};
@@ -362,7 +365,7 @@ export default function EditorLayout() {
await refreshStacks();
} catch (error) {
console.error('Failed to delete stack:', error);
- alert('Failed to delete stack');
+ toast.error('Failed to delete stack');
} finally {
setIsActionLoading(false);
}
@@ -392,7 +395,7 @@ export default function EditorLayout() {
await loadFile(stackName);
} catch (error: any) {
console.error('Failed to create stack:', error);
- alert(error.message || 'Failed to create stack');
+ toast.error(error.message || 'Failed to create stack');
}
};
@@ -460,8 +463,10 @@ export default function EditorLayout() {
Create New Stack
-
+
+
setNewStackName(e.target.value)}
@@ -764,20 +769,20 @@ export default function EditorLayout() {
{/* Delete Confirmation Dialog */}
-
+
+
+
+ setDeleteDialogOpen(false)}>Cancel
+ Delete
+
+
+
{/* Bash Exec Modal */}
{selectedContainer && (
diff --git a/frontend/src/components/HomeDashboard.tsx b/frontend/src/components/HomeDashboard.tsx
index 87499149..80e0e887 100644
--- a/frontend/src/components/HomeDashboard.tsx
+++ b/frontend/src/components/HomeDashboard.tsx
@@ -5,6 +5,8 @@ import { Input } from './ui/input';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from './ui/dialog';
import { Activity, Square, PauseCircle, ArrowRight, Plus, Cpu, HardDrive, MemoryStick } from 'lucide-react';
import { apiFetch } from '@/lib/api';
+import { toast } from 'sonner';
+import { Label } from './ui/label';
interface Stats {
active: number;
@@ -96,7 +98,7 @@ export default function HomeDashboard() {
setConvertedYaml(data.yaml);
} catch (error) {
console.error('Conversion error:', error);
- alert('Failed to convert docker run command');
+ toast.error('Failed to convert docker run command');
} finally {
setIsConverting(false);
}
@@ -128,7 +130,7 @@ export default function HomeDashboard() {
window.location.reload(); // Refresh to show new stack
} catch (error) {
console.error('Failed to create stack:', error);
- alert('Failed to create stack');
+ toast.error('Failed to create stack');
}
};
@@ -201,7 +203,7 @@ export default function HomeDashboard() {
{systemStats ? `${systemStats.memory.usagePercent}%` : '...'}
- {systemStats
+ {systemStats
? `${formatBytes(systemStats.memory.used)} / ${formatBytes(systemStats.memory.total)}`
: 'Loading...'}
@@ -218,7 +220,7 @@ export default function HomeDashboard() {
{systemStats?.disk ? `${systemStats.disk.usagePercent}%` : '...'}
- {systemStats?.disk
+ {systemStats?.disk
? `${formatBytes(systemStats.disk.used)} / ${formatBytes(systemStats.disk.total)}`
: 'Loading...'}
@@ -241,7 +243,7 @@ export default function HomeDashboard() {
value={dockerRunInput}
onChange={(e) => setDockerRunInput(e.target.value)}
/>
-
+