diff --git a/backend/src/index.ts b/backend/src/index.ts index d48d2a1b..650cf525 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -976,6 +976,79 @@ app.get('/api/system/docker-df', async (req: Request, res: Response) => { } }); +app.get('/api/system/images', async (req: Request, res: Response) => { + try { + const dockerController = DockerController.getInstance(); + const images = await dockerController.getImages(); + res.json(images); + } catch (error) { + console.error('Failed to fetch images:', error); + res.status(500).json({ error: 'Failed to fetch images' }); + } +}); + +app.get('/api/system/volumes', async (req: Request, res: Response) => { + try { + const dockerController = DockerController.getInstance(); + const volumes = await dockerController.getVolumes(); + res.json(volumes); + } catch (error) { + console.error('Failed to fetch volumes:', error); + res.status(500).json({ error: 'Failed to fetch volumes' }); + } +}); + +app.get('/api/system/networks', async (req: Request, res: Response) => { + try { + const dockerController = DockerController.getInstance(); + const networks = await dockerController.getNetworks(); + res.json(networks); + } catch (error) { + console.error('Failed to fetch networks:', error); + res.status(500).json({ error: 'Failed to fetch networks' }); + } +}); + +app.post('/api/system/images/delete', async (req: Request, res: Response) => { + try { + const { id } = req.body; + if (!id) return res.status(400).json({ error: 'ID is required' }); + const dockerController = DockerController.getInstance(); + await dockerController.removeImage(id); + res.json({ success: true, message: 'Image deleted' }); + } catch (error: any) { + console.error('Failed to delete image:', error); + res.status(500).json({ error: error.message || 'Failed to delete image' }); + } +}); + +app.post('/api/system/volumes/delete', async (req: Request, res: Response) => { + try { + const { id } = req.body; + if (!id) return res.status(400).json({ error: 'ID is required' }); + const dockerController = DockerController.getInstance(); + await dockerController.removeVolume(id); + res.json({ success: true, message: 'Volume deleted' }); + } catch (error: any) { + console.error('Failed to delete volume:', error); + res.status(500).json({ error: error.message || 'Failed to delete volume' }); + } +}); + +app.post('/api/system/networks/delete', async (req: Request, res: Response) => { + try { + const { id } = req.body; + if (!id) return res.status(400).json({ error: 'ID is required' }); + const dockerController = DockerController.getInstance(); + await dockerController.removeNetwork(id); + res.json({ success: true, message: 'Network deleted' }); + } catch (error: any) { + console.error('Failed to delete network:', error); + res.status(500).json({ error: error.message || 'Failed to delete network' }); + } +}); + + // Serve static files in production (for Docker deployment) if (process.env.NODE_ENV === 'production') { app.use(express.static('public')); diff --git a/backend/src/services/DockerController.ts b/backend/src/services/DockerController.ts index f8ae97bf..6164d646 100644 --- a/backend/src/services/DockerController.ts +++ b/backend/src/services/DockerController.ts @@ -83,6 +83,34 @@ class DockerController { }; } + public async getImages() { + return await this.docker.listImages({ all: false }); + } + + public async getVolumes() { + const data = await this.docker.listVolumes(); + return data.Volumes || []; + } + + public async getNetworks() { + return await this.docker.listNetworks(); + } + + public async removeImage(id: string) { + const image = this.docker.getImage(id); + await image.remove({ force: true }); + } + + public async removeVolume(name: string) { + const volume = this.docker.getVolume(name); + await volume.remove({ force: true }); + } + + public async removeNetwork(id: string) { + const network = this.docker.getNetwork(id); + await network.remove({ force: true }); + } + public async getRunningContainers() { const containers = await this.docker.listContainers({ all: false }); return containers; diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx index a0338e9a..ea348909 100644 --- a/frontend/src/components/EditorLayout.tsx +++ b/frontend/src/components/EditorLayout.tsx @@ -5,7 +5,7 @@ import ErrorBoundary from './ErrorBoundary'; import HomeDashboard from './HomeDashboard'; import BashExecModal from './BashExecModal'; import HostConsole from './HostConsole'; -import MaintenanceModal from './MaintenanceModal'; +import ResourcesView from './ResourcesView'; import { Button } from './ui/button'; import { Input } from './ui/input'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogTrigger } from './ui/dialog'; @@ -13,7 +13,7 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, 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, RotateCw, CloudDownload, Pencil, X, Home, LogOut, Brush, ExternalLink, Bell, Settings, MoreVertical, BellRing, Rocket } from 'lucide-react'; +import { Plus, Trash2, Play, Square, Save, Terminal, RotateCw, CloudDownload, Pencil, X, Home, LogOut, ExternalLink, Bell, Settings, MoreVertical, BellRing, Rocket, HardDrive } from 'lucide-react'; import { useAuth } from '@/context/AuthContext'; import { apiFetch } from '@/lib/api'; import { toast } from 'sonner'; @@ -76,7 +76,7 @@ export default function EditorLayout() { } return true; // Default to dark mode }); - const [activeView, setActiveView] = useState<'dashboard' | 'editor' | 'host-console'>('dashboard'); + const [activeView, setActiveView] = useState<'dashboard' | 'editor' | 'host-console' | 'resources'>('dashboard'); const [isEditing, setIsEditing] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [stackStatuses, setStackStatuses] = useState({}); @@ -85,8 +85,6 @@ export default function EditorLayout() { const [bashModalOpen, setBashModalOpen] = useState(false); const [selectedContainer, setSelectedContainer] = useState<{ id: string; name: string } | null>(null); - // Maintenance modal state - const [maintenanceModalOpen, setMaintenanceModalOpen] = useState(false); // Notifications & Settings state const [notifications, setNotifications] = useState([]); @@ -756,16 +754,16 @@ export default function EditorLayout() { Console - {/* System Janitor (Maintenance) Toggle */} + {/* Resources Toggle */} {/* Settings Modal Toggle */} @@ -850,7 +848,9 @@ export default function EditorLayout() { {/* Main Workspace */}
- {activeView === 'host-console' ? ( + {activeView === 'resources' ? ( + + ) : activeView === 'host-console' ? ( setActiveView(selectedFile ? 'editor' : 'dashboard')} /> ) : !isLoading && selectedFile && activeView === 'editor' ? ( @@ -1132,10 +1132,6 @@ export default function EditorLayout() { /> )} - setMaintenanceModalOpen(false)} - /> {/* Settings Modal */} Resources Hub (Under Construction)
; +}