'use client' import { useState, useMemo } from 'react' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Loader2, Pencil, X, Check, Package, CheckSquare, Square, } from 'lucide-react' // All available tools from the stacks folder export const ALL_TOOLS: Record = { // Core Infrastructure orchestrator: { name: 'Orchestrator', description: 'LetsBe control plane API', category: 'Core' }, sysadmin: { name: 'SysAdmin Agent', description: 'Remote automation worker', category: 'Core' }, portainer: { name: 'Portainer', description: 'Container management UI', category: 'Core' }, // Communication poste: { name: 'Poste.io', description: 'Email server with webmail', category: 'Communication' }, chatwoot: { name: 'Chatwoot', description: 'Customer engagement platform', category: 'Communication' }, listmonk: { name: 'Listmonk', description: 'Newsletter & mailing list manager', category: 'Communication' }, // File Storage & Collaboration nextcloud: { name: 'Nextcloud', description: 'File sync & collaboration', category: 'Files' }, minio: { name: 'MinIO', description: 'S3-compatible object storage', category: 'Files' }, documenso: { name: 'Documenso', description: 'Document signing platform', category: 'Files' }, // Identity & Security keycloak: { name: 'Keycloak', description: 'Identity & access management', category: 'Security' }, vaultwarden: { name: 'Vaultwarden', description: 'Password manager (Bitwarden)', category: 'Security' }, // Automation & Workflows n8n: { name: 'n8n', description: 'Workflow automation', category: 'Automation' }, activepieces: { name: 'Activepieces', description: 'No-code automation platform', category: 'Automation' }, windmill: { name: 'Windmill', description: 'Developer-first workflows', category: 'Automation' }, typebot: { name: 'Typebot', description: 'Conversational app builder', category: 'Automation' }, // Development gitea: { name: 'Gitea', description: 'Git server with web UI', category: 'Development' }, 'gitea-drone': { name: 'Drone CI', description: 'Continuous integration with Gitea', category: 'Development' }, // Databases & Analytics nocodb: { name: 'NocoDB', description: 'Airtable alternative database UI', category: 'Data' }, redash: { name: 'Redash', description: 'Data visualization & dashboards', category: 'Data' }, umami: { name: 'Umami', description: 'Privacy-focused web analytics', category: 'Data' }, // AI & Chat librechat: { name: 'LibreChat', description: 'ChatGPT-style AI interface', category: 'AI' }, // CMS & Content ghost: { name: 'Ghost', description: 'Publishing platform & blog', category: 'Content' }, wordpress: { name: 'WordPress', description: 'Content management system', category: 'Content' }, squidex: { name: 'Squidex', description: 'Headless CMS', category: 'Content' }, // Business Tools calcom: { name: 'Cal.com', description: 'Scheduling & calendar booking', category: 'Business' }, odoo: { name: 'Odoo', description: 'ERP & business apps suite', category: 'Business' }, penpot: { name: 'Penpot', description: 'Design & prototyping platform', category: 'Business' }, // Monitoring & Maintenance glitchtip: { name: 'GlitchTip', description: 'Error tracking (Sentry alt)', category: 'Monitoring' }, 'uptime-kuma': { name: 'Uptime Kuma', description: 'Uptime monitoring', category: 'Monitoring' }, 'diun-watchtower': { name: 'Diun/Watchtower', description: 'Container update notifications', category: 'Monitoring' }, // Other html: { name: 'Static HTML', description: 'Simple static website hosting', category: 'Other' }, } // Default tools that are always recommended export const DEFAULT_TOOLS = ['html', 'portainer', 'diun-watchtower'] // Tool dependencies: selecting a tool automatically selects its dependencies const TOOL_DEPENDENCIES: Record = { 'gitea': ['gitea-drone'], // Gitea requires Drone CI 'ghost': ['html'], // CMS tools require HTML 'wordpress': ['html'], 'squidex': ['html'], } // Category order for display const CATEGORY_ORDER = [ 'Core', 'Communication', 'Files', 'Security', 'Automation', 'Development', 'Data', 'AI', 'Content', 'Business', 'Monitoring', 'Other', ] interface ToolsEditorProps { tools: string[] onSave: (tools: string[]) => Promise isEditable: boolean isSaving?: boolean } export function ToolsEditor({ tools, onSave, isEditable, isSaving = false }: ToolsEditorProps) { const [isEditing, setIsEditing] = useState(false) const [selectedTools, setSelectedTools] = useState(tools) const [saving, setSaving] = useState(false) // Group tools by category const toolsByCategory = useMemo(() => { const grouped: Record = {} for (const [key, info] of Object.entries(ALL_TOOLS)) { if (!grouped[info.category]) { grouped[info.category] = [] } grouped[info.category].push(key) } return grouped }, []) const handleToggleTool = (tool: string) => { setSelectedTools((prev) => { if (prev.includes(tool)) { // Removing a tool - also remove tools that depend on it const newTools = prev.filter((t) => t !== tool) // If removing gitea, also remove gitea-drone if (tool === 'gitea') { newTools = newTools.filter((t) => t !== 'gitea-drone') } return newTools } else { // Adding a tool - also add its dependencies const newTools = [...prev, tool] const deps = TOOL_DEPENDENCIES[tool] if (deps) { deps.forEach((dep) => { if (!newTools.includes(dep)) { newTools.push(dep) } }) } return newTools } }) } const handleSave = async () => { setSaving(true) try { await onSave(selectedTools) setIsEditing(false) } catch (error) { console.error('Failed to save tools:', error) } finally { setSaving(false) } } const handleCancel = () => { setSelectedTools(tools) setIsEditing(false) } // Display mode - just show selected tools if (!isEditing) { return (
Selected Tools Tools to be deployed on this server
{isEditable && ( )}
{tools.length === 0 ? (

No tools selected

) : (
{tools.map((tool) => { const toolInfo = ALL_TOOLS[tool] return ( {toolInfo?.name || tool} ) })}
)}
) } // Edit mode - show all tools grouped by category with checkboxes return (
Edit Tools Select which tools to deploy ({selectedTools.length} selected)
{CATEGORY_ORDER.map((category) => { const categoryTools = toolsByCategory[category] if (!categoryTools) return null return (

{category}

{categoryTools.map((toolKey) => { const tool = ALL_TOOLS[toolKey] const isSelected = selectedTools.includes(toolKey) return ( ) })}
) })}
) }