mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-27 10:46:51 +00:00
feat: add Docker API endpoints for managing images, volumes, and networks; implement Resources view in editor
This commit is contained in:
@@ -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)
|
// Serve static files in production (for Docker deployment)
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
app.use(express.static('public'));
|
app.use(express.static('public'));
|
||||||
|
|||||||
@@ -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() {
|
public async getRunningContainers() {
|
||||||
const containers = await this.docker.listContainers({ all: false });
|
const containers = await this.docker.listContainers({ all: false });
|
||||||
return containers;
|
return containers;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ErrorBoundary from './ErrorBoundary';
|
|||||||
import HomeDashboard from './HomeDashboard';
|
import HomeDashboard from './HomeDashboard';
|
||||||
import BashExecModal from './BashExecModal';
|
import BashExecModal from './BashExecModal';
|
||||||
import HostConsole from './HostConsole';
|
import HostConsole from './HostConsole';
|
||||||
import MaintenanceModal from './MaintenanceModal';
|
import ResourcesView from './ResourcesView';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { Input } from './ui/input';
|
import { Input } from './ui/input';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogTrigger } from './ui/dialog';
|
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 { Tabs, TabsList, TabsTrigger } from './ui/tabs';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
||||||
import { Badge } from './ui/badge';
|
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 { useAuth } from '@/context/AuthContext';
|
||||||
import { apiFetch } from '@/lib/api';
|
import { apiFetch } from '@/lib/api';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -76,7 +76,7 @@ export default function EditorLayout() {
|
|||||||
}
|
}
|
||||||
return true; // Default to dark mode
|
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 [isEditing, setIsEditing] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [stackStatuses, setStackStatuses] = useState<StackStatus>({});
|
const [stackStatuses, setStackStatuses] = useState<StackStatus>({});
|
||||||
@@ -85,8 +85,6 @@ export default function EditorLayout() {
|
|||||||
const [bashModalOpen, setBashModalOpen] = useState(false);
|
const [bashModalOpen, setBashModalOpen] = useState(false);
|
||||||
const [selectedContainer, setSelectedContainer] = useState<{ id: string; name: string } | null>(null);
|
const [selectedContainer, setSelectedContainer] = useState<{ id: string; name: string } | null>(null);
|
||||||
|
|
||||||
// Maintenance modal state
|
|
||||||
const [maintenanceModalOpen, setMaintenanceModalOpen] = useState(false);
|
|
||||||
|
|
||||||
// Notifications & Settings state
|
// Notifications & Settings state
|
||||||
const [notifications, setNotifications] = useState<any[]>([]);
|
const [notifications, setNotifications] = useState<any[]>([]);
|
||||||
@@ -756,16 +754,16 @@ export default function EditorLayout() {
|
|||||||
<Terminal className="w-4 h-4 mr-2" />
|
<Terminal className="w-4 h-4 mr-2" />
|
||||||
Console
|
Console
|
||||||
</Button>
|
</Button>
|
||||||
{/* System Janitor (Maintenance) Toggle */}
|
{/* Resources Toggle */}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
onClick={() => setMaintenanceModalOpen(true)}
|
onClick={() => setActiveView('resources')}
|
||||||
title="System Maintenance"
|
title="System Resources"
|
||||||
>
|
>
|
||||||
<Brush className="w-4 h-4 mr-2" />
|
<HardDrive className="w-4 h-4 mr-2" />
|
||||||
Janitor
|
Resources
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Settings Modal Toggle */}
|
{/* Settings Modal Toggle */}
|
||||||
@@ -850,7 +848,9 @@ export default function EditorLayout() {
|
|||||||
|
|
||||||
{/* Main Workspace */}
|
{/* Main Workspace */}
|
||||||
<div className="flex-1 overflow-y-auto p-6">
|
<div className="flex-1 overflow-y-auto p-6">
|
||||||
{activeView === 'host-console' ? (
|
{activeView === 'resources' ? (
|
||||||
|
<ResourcesView />
|
||||||
|
) : activeView === 'host-console' ? (
|
||||||
<HostConsole stackName={selectedFile} onClose={() => setActiveView(selectedFile ? 'editor' : 'dashboard')} />
|
<HostConsole stackName={selectedFile} onClose={() => setActiveView(selectedFile ? 'editor' : 'dashboard')} />
|
||||||
) : !isLoading && selectedFile && activeView === 'editor' ? (
|
) : !isLoading && selectedFile && activeView === 'editor' ? (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
@@ -1132,10 +1132,6 @@ export default function EditorLayout() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MaintenanceModal
|
|
||||||
isOpen={maintenanceModalOpen}
|
|
||||||
onClose={() => setMaintenanceModalOpen(false)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Settings Modal */}
|
{/* Settings Modal */}
|
||||||
<SettingsModal
|
<SettingsModal
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export default function ResourcesView() {
|
||||||
|
return <div className="p-6 text-foreground text-2xl font-bold">Resources Hub (Under Construction)</div>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user