diff --git a/frontend/src/app/(dashboard)/agents/_components/agents-grid.tsx b/frontend/src/app/(dashboard)/agents/_components/agents-grid.tsx index bf6c9c77..b5c4bb2e 100644 --- a/frontend/src/app/(dashboard)/agents/_components/agents-grid.tsx +++ b/frontend/src/app/(dashboard)/agents/_components/agents-grid.tsx @@ -42,65 +42,107 @@ const PublishingChoiceDialog = ({ agent, isOpen, onClose, onRegularPublish, onSe - Choose Publishing Method + Publish to Marketplace - How would you like to publish "{agent.name}" to the marketplace? + Publish "{agent.name}" to the marketplace for others to discover and use.
- {hasMCPCredentials && ( -
-
- -
-

This agent contains MCP credentials

-

We recommend using secure publishing to protect your API keys.

+ {hasMCPCredentials ? ( + <> +
+
+ +
+

Secure Publishing Recommended

+

This agent contains MCP credentials. We'll create a secure template that protects your API keys.

+
+ +
+ + +
+ + Advanced: Legacy publishing options + +
+ +
+
+
+ + ) : ( +
+ + +
)} - -
- - - -
@@ -268,7 +310,7 @@ export const AgentsGrid = ({ const handleRegularPublish = async (agentId: string) => { try { await publishAgentMutation.mutateAsync({ agentId, tags: [] }); - toast.success('Agent published to marketplace successfully!'); + toast.success('Agent published to marketplace! Note: Any embedded credentials will be visible to users.'); setShowPublishingChoice(false); setPublishingAgent(null); } catch (error: any) { @@ -283,7 +325,7 @@ export const AgentsGrid = ({ make_public: true, tags: [] }); - toast.success('Secure template created successfully!'); + toast.success('Agent published as secure template! Users will use their own encrypted credentials.'); setShowPublishingChoice(false); setPublishingAgent(null); } catch (error: any) { diff --git a/frontend/src/app/(dashboard)/marketplace/page.tsx b/frontend/src/app/(dashboard)/marketplace/page.tsx index 4a4380b4..5db88f6b 100644 --- a/frontend/src/app/(dashboard)/marketplace/page.tsx +++ b/frontend/src/app/(dashboard)/marketplace/page.tsx @@ -1,29 +1,578 @@ 'use client'; import React, { useState, useMemo } from 'react'; -import { Search, Download, Star, Calendar, User, Tags, TrendingUp, Globe, Shield } from 'lucide-react'; +import { Search, Download, Star, Calendar, User, Tags, TrendingUp, Globe, Shield, AlertTriangle, CheckCircle, Loader2, Settings, X, Wrench, Zap } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { useMarketplaceAgents, useAddAgentToLibrary } from '@/hooks/react-query/marketplace/use-marketplace'; import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle } from '@/components/ui/drawer'; +import { Card, CardContent } from '@/components/ui/card'; +import { Separator } from '@/components/ui/separator'; import { toast } from 'sonner'; import { getAgentAvatar } from '../agents/_utils/get-agent-style'; import { Skeleton } from '@/components/ui/skeleton'; import { Pagination } from '../agents/_components/pagination'; import Link from 'next/link'; +import { useMediaQuery } from '@/hooks/use-media-query'; + +import { useMarketplaceAgents, useAddAgentToLibrary } from '@/hooks/react-query/marketplace/use-marketplace'; + +import { + useMarketplaceTemplates, + useInstallTemplate, + useUserCredentials +} from '@/hooks/react-query/secure-mcp/use-secure-mcp'; type SortOption = 'newest' | 'popular' | 'most_downloaded' | 'name'; +type ViewMode = 'all' | 'legacy' | 'secure'; -export default function MarketplacePage() { +interface UnifiedMarketplaceItem { + id: string; + name: string; + description: string; + tags: string[]; + download_count: number; + creator_name: string; + created_at: string; + marketplace_published_at?: string; + avatar?: string; + avatar_color?: string; + type: 'legacy' | 'secure'; + // Legacy agent fields + agent_id?: string; + // Secure template fields + template_id?: string; + mcp_requirements?: Array<{ + qualified_name: string; + display_name: string; + enabled_tools?: string[]; + }>; +} + +interface InstallDialogProps { + item: UnifiedMarketplaceItem | null; + open: boolean; + onOpenChange: (open: boolean) => void; + onInstall: (item: UnifiedMarketplaceItem, instanceName?: string) => void; + isInstalling: boolean; + credentialStatus?: { + hasAllCredentials: boolean; + missingCount: number; + totalRequired: number; + }; +} + +const InstallDialog: React.FC = ({ + item, + open, + onOpenChange, + onInstall, + isInstalling, + credentialStatus +}) => { + const [instanceName, setInstanceName] = useState(''); + + React.useEffect(() => { + if (item) { + if (item.type === 'secure') { + setInstanceName(`${item.name} (My Copy)`); + } else { + setInstanceName(''); + } + } + }, [item]); + + if (!item) return null; + + return ( + + + + + {item.type === 'secure' ? 'Install Secure Template' : 'Add Agent to Library'} + {item.type === 'secure' && ( + + + Secure + + )} + + + {item.type === 'secure' + ? `Install "${item.name}" as a secure agent instance` + : `Add "${item.name}" to your agent library` + } + + + +
+ {item.type === 'secure' && ( +
+ + setInstanceName(e.target.value)} + placeholder="Enter a name for your agent" + /> +
+ )} + + {item.type === 'secure' && item.mcp_requirements && item.mcp_requirements.length > 0 && ( +
+ +
+ {item.mcp_requirements.map((req) => ( +
+
+

{req.display_name}

+

{req.qualified_name}

+
+ + {req.enabled_tools?.length || 0} tools + +
+ ))} +
+ + {credentialStatus && !credentialStatus.hasAllCredentials && ( + + + + You're missing credentials for {credentialStatus.missingCount} of {credentialStatus.totalRequired} required services. + + Set them up first → + + + + )} + + {credentialStatus && credentialStatus.hasAllCredentials && ( + + + + All required credentials are configured. This agent will use your encrypted API keys. + + + )} +
+ )} + + {item.type === 'legacy' && ( + + + + This is a legacy agent that may contain embedded API keys. Consider using secure templates for better security. + + + )} +
+ + + + + +
+
+ ); +}; + +interface MissingCredentialsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + missingCredentials: Array<{ + qualified_name: string; + display_name: string; + required_config: string[]; + }>; + templateName: string; +} + +const MissingCredentialsDialog: React.FC = ({ + open, + onOpenChange, + missingCredentials, + templateName +}) => { + return ( + + + + Missing Credentials + + "{templateName}" requires MCP credentials that you haven't set up yet. + + + +
+ + + + You need to configure credentials for the following MCP services before installing this agent. + + + +
+ {missingCredentials.map((cred) => ( + + +
+

{cred.display_name}

+

{cred.qualified_name}

+
+ {cred.required_config.map((config) => ( + + {config} + + ))} +
+
+
+
+ ))} +
+
+ + + + + +
+
+ ); +}; + +interface AgentDetailsProps { + item: UnifiedMarketplaceItem | null; + open: boolean; + onOpenChange: (open: boolean) => void; + onInstall: (item: UnifiedMarketplaceItem, instanceName?: string) => void; + isInstalling: boolean; + credentialStatus?: { + hasAllCredentials: boolean; + missingCount: number; + totalRequired: number; + }; +} + +const getItemStyling = (item: UnifiedMarketplaceItem) => { + if (item.avatar && item.avatar_color) { + return { + avatar: item.avatar, + color: item.avatar_color, + }; + } + return getAgentAvatar(item.id); +}; + +const AgentDetailsContent: React.FC<{ + item: UnifiedMarketplaceItem; + instanceName: string; + setInstanceName: (name: string) => void; +}> = ({ item, instanceName, setInstanceName }) => { + const { avatar, color } = getItemStyling(item); + + return ( +
+
+
+
+ {avatar} +
+
+
+

{item.name}

+ {item.type === 'secure' ? ( + + + Secure + + ) : ( + + + Legacy + + )} +
+

{item.description}

+
+
+
+
+ + By {item.creator_name} +
+
+ + {item.download_count} downloads +
+ {item.marketplace_published_at && ( +
+ + {new Date(item.marketplace_published_at).toLocaleDateString()} +
+ )} +
+ {item.tags && item.tags.length > 0 && ( +
+ {item.tags.map(tag => ( + + + {tag} + + ))} +
+ )} +
+ + + + {item.type === 'secure' && item.mcp_requirements && item.mcp_requirements.length > 0 && ( +
+
+

Required MCP Services

+
+ +
+ {item.mcp_requirements.map((req) => ( + + +
+
+
+
+ +
+

{req.display_name}

+
+ {req.enabled_tools && req.enabled_tools.length > 0 && ( +
+ {req.enabled_tools.map(tool => ( + + + {tool} + + ))} +
+ )} +
+
+
+
+ ))} +
+ + +
+ )} + + {item.type === 'legacy' && ( + + + + This is a legacy agent that may contain embedded API keys. Consider using secure templates for better security. + + + )} + + {item.type === 'secure' && ( +
+ + setInstanceName(e.target.value)} + placeholder="Enter a name for your agent" + /> +
+ )} +
+ ); +}; + +const AgentDetailsSheet: React.FC = ({ + item, + open, + onOpenChange, + onInstall, + isInstalling, + credentialStatus +}) => { + const isDesktop = useMediaQuery("(min-width: 768px)"); + const [instanceName, setInstanceName] = useState(''); + + React.useEffect(() => { + if (item) { + if (item.type === 'secure') { + setInstanceName(`${item.name} (My Copy)`); + } else { + setInstanceName(''); + } + } + }, [item]); + + if (!item) return null; + if (isDesktop) { + return ( + + + + Agent Details + + View details and install this {item.type === 'secure' ? 'secure template' : 'agent'} + + +
+ +
+
+ {credentialStatus && !credentialStatus.hasAllCredentials && ( + + + + You're missing credentials for {credentialStatus.missingCount} of {credentialStatus.totalRequired} required services. + + Set them up first → + + + + )} + +
+
+
+ ); + } + + return ( + + + + Agent Details + + View details and install this {item.type === 'secure' ? 'secure template' : 'agent'} + + +
+ +
+
+ {credentialStatus && !credentialStatus.hasAllCredentials && ( + + + + You're missing credentials for {credentialStatus.missingCount} of {credentialStatus.totalRequired} required services. + + Set them up first → + + + + )} + +
+
+
+ ); +}; + +export default function UnifiedMarketplacePage() { const [page, setPage] = useState(1); const [searchQuery, setSearchQuery] = useState(''); const [selectedTags, setSelectedTags] = useState([]); const [sortBy, setSortBy] = useState('newest'); - const [addingAgentId, setAddingAgentId] = useState(null); - - const queryParams = useMemo(() => ({ + const [viewMode, setViewMode] = useState('all'); + const [installingItemId, setInstallingItemId] = useState(null); + const [selectedItem, setSelectedItem] = useState(null); + const [showInstallDialog, setShowInstallDialog] = useState(false); + const [showDetailsSheet, setShowDetailsSheet] = useState(false); + const [showMissingCredsDialog, setShowMissingCredsDialog] = useState(false); + const [missingCredentials, setMissingCredentials] = useState([]); + + // Legacy marketplace data + const legacyQueryParams = useMemo(() => ({ page, limit: 20, search: searchQuery || undefined, @@ -31,29 +580,161 @@ export default function MarketplacePage() { sort_by: sortBy }), [page, searchQuery, selectedTags, sortBy]); - const { data: agentsResponse, isLoading, error } = useMarketplaceAgents(queryParams); + const { data: legacyAgentsResponse, isLoading: isLoadingLegacy } = useMarketplaceAgents( + viewMode === 'secure' ? { page: 1, limit: 0 } : legacyQueryParams + ); const addToLibraryMutation = useAddAgentToLibrary(); - const agents = agentsResponse?.agents || []; - const pagination = agentsResponse?.pagination; + // Secure marketplace data + const secureQueryParams = useMemo(() => ({ + limit: 20, + offset: (page - 1) * 20, + search: searchQuery || undefined, + tags: selectedTags.length > 0 ? selectedTags.join(',') : undefined, + }), [page, searchQuery, selectedTags]); + + const { data: secureTemplates, isLoading: isLoadingSecure } = useMarketplaceTemplates( + viewMode === 'legacy' ? { limit: 0, offset: 0 } : secureQueryParams + ); + const { data: userCredentials } = useUserCredentials(); + const installTemplateMutation = useInstallTemplate(); + + // Combine and transform data + const unifiedItems = useMemo(() => { + const items: UnifiedMarketplaceItem[] = []; + + // Add legacy agents + if (legacyAgentsResponse?.agents && viewMode !== 'secure') { + legacyAgentsResponse.agents.forEach(agent => { + items.push({ + id: `legacy_${agent.agent_id}`, + name: agent.name, + description: agent.description, + tags: agent.tags || [], + download_count: agent.download_count || 0, + creator_name: agent.creator_name, + created_at: agent.created_at, + marketplace_published_at: agent.marketplace_published_at, + avatar: agent.avatar, + avatar_color: agent.avatar_color, + type: 'legacy', + agent_id: agent.agent_id, + }); + }); + } + + // Add secure templates + if (secureTemplates && viewMode !== 'legacy') { + secureTemplates.forEach(template => { + items.push({ + id: `secure_${template.template_id}`, + name: template.name, + description: template.description, + tags: template.tags || [], + download_count: template.download_count || 0, + creator_name: template.creator_name || 'Anonymous', + created_at: template.created_at, + marketplace_published_at: template.marketplace_published_at, + avatar: template.avatar, + avatar_color: template.avatar_color, + type: 'secure', + template_id: template.template_id, + mcp_requirements: template.mcp_requirements, + }); + }); + } + + // Sort items + return items.sort((a, b) => { + switch (sortBy) { + case 'newest': + return new Date(b.marketplace_published_at || b.created_at).getTime() - + new Date(a.marketplace_published_at || a.created_at).getTime(); + case 'popular': + case 'most_downloaded': + return b.download_count - a.download_count; + case 'name': + return a.name.localeCompare(b.name); + default: + return 0; + } + }); + }, [legacyAgentsResponse, secureTemplates, sortBy, viewMode]); + + const isLoading = isLoadingLegacy || isLoadingSecure; + const pagination = legacyAgentsResponse?.pagination; React.useEffect(() => { setPage(1); - }, [searchQuery, selectedTags, sortBy]); + }, [searchQuery, selectedTags, sortBy, viewMode]); - const handleAddToLibrary = async (agentId: string, agentName: string) => { + const getUserCredentialNames = () => { + return new Set(userCredentials?.map(cred => cred.mcp_qualified_name) || []); + }; + + const getItemCredentialStatus = (item: UnifiedMarketplaceItem) => { + if (item.type !== 'secure' || !item.mcp_requirements) { + return { hasAllCredentials: true, missingCount: 0, totalRequired: 0 }; + } + + const userCredNames = getUserCredentialNames(); + const requiredCreds = item.mcp_requirements.map(req => req.qualified_name); + const missingCreds = requiredCreds.filter(cred => !userCredNames.has(cred)); + + return { + hasAllCredentials: missingCreds.length === 0, + missingCount: missingCreds.length, + totalRequired: requiredCreds.length + }; + }; + + const handleItemClick = (item: UnifiedMarketplaceItem) => { + setSelectedItem(item); + setShowDetailsSheet(true); + }; + + const handleInstallClick = (item: UnifiedMarketplaceItem, e?: React.MouseEvent) => { + if (e) { + e.stopPropagation(); + } + setSelectedItem(item); + setShowInstallDialog(true); + }; + + const handleInstall = async (item: UnifiedMarketplaceItem, instanceName?: string) => { + setInstallingItemId(item.id); + try { - setAddingAgentId(agentId); - await addToLibraryMutation.mutateAsync(agentId); - toast.success(`${agentName} has been added to your library!`); + if (item.type === 'legacy' && item.agent_id) { + await addToLibraryMutation.mutateAsync(item.agent_id); + toast.success(`${item.name} has been added to your library!`); + setShowInstallDialog(false); + setShowDetailsSheet(false); + } else if (item.type === 'secure' && item.template_id) { + const result = await installTemplateMutation.mutateAsync({ + template_id: item.template_id, + instance_name: instanceName + }); + + if (result.status === 'installed') { + toast.success('Agent installed successfully!'); + setShowInstallDialog(false); + setShowDetailsSheet(false); + } else if (result.status === 'credentials_required') { + setMissingCredentials(result.missing_credentials || []); + setShowInstallDialog(false); + setShowDetailsSheet(false); + setShowMissingCredsDialog(true); + } + } } catch (error: any) { if (error.message?.includes('already in your library')) { toast.error('This agent is already in your library'); } else { - toast.error('Failed to add agent to library'); + toast.error(error.message || 'Failed to install agent'); } } finally { - setAddingAgentId(null); + setInstallingItemId(null); } }; @@ -65,69 +746,87 @@ export default function MarketplacePage() { ); }; - const getAgentStyling = (agent: any) => { - if (agent.avatar && agent.avatar_color) { + const getItemStyling = (item: UnifiedMarketplaceItem) => { + if (item.avatar && item.avatar_color) { return { - avatar: agent.avatar, - color: agent.avatar_color, + avatar: item.avatar, + color: item.avatar_color, }; } - return getAgentAvatar(agent.agent_id); + return getAgentAvatar(item.id); }; const allTags = React.useMemo(() => { const tags = new Set(); - agents.forEach(agent => { - agent.tags?.forEach(tag => tags.add(tag)); + unifiedItems.forEach(item => { + item.tags?.forEach(tag => tags.add(tag)); }); return Array.from(tags); - }, [agents]); + }, [unifiedItems]); - if (error) { - return ( -
- - - Failed to load marketplace agents. Please try again later. - - -
- ); - } + const getSecureCount = () => unifiedItems.filter(item => item.type === 'secure').length; + const getLegacyCount = () => unifiedItems.filter(item => item.type === 'legacy').length; return (
-
-

- Agent Marketplace -

- -
+

+ Agent Marketplace +

- Discover and add powerful AI agents created by the community to your personal library + Discover and add powerful AI agents created by the community

+ + + + + + Manage your credentials → + + +
setSearchQuery(e.target.value)} className="pl-10" />
+ + setInstanceName(e.target.value)} - placeholder="Enter a name for your agent" - /> -
- - {template.mcp_requirements.length > 0 && ( -
- -
- {template.mcp_requirements.map((req) => ( -
-
-

{req.display_name}

-

{req.qualified_name}

-
- - {req.enabled_tools?.length || 0} tools - -
- ))} -
- - - - This agent requires MCP credentials. If you haven't set them up yet, you'll be prompted to configure them. - - -
- )} -
- - - - - - - - ); -}; - -interface MissingCredentialsDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - missingCredentials: Array<{ - qualified_name: string; - display_name: string; - required_config: string[]; - }>; - templateName: string; -} - -const MissingCredentialsDialog: React.FC = ({ - open, - onOpenChange, - missingCredentials, - templateName -}) => { - return ( - - - - Missing Credentials - - "{templateName}" requires MCP credentials that you haven't set up yet. - - - -
- - - - You need to configure credentials for the following MCP services before installing this agent. - - - -
- {missingCredentials.map((cred) => ( - - -
-

{cred.display_name}

-

{cred.qualified_name}

-
- {cred.required_config.map((config) => ( - - {config} - - ))} -
-
-
-
- ))} -
-
- - - - - -
-
- ); -}; - -export default function SecureMarketplacePage() { - const [page, setPage] = useState(1); - const [searchQuery, setSearchQuery] = useState(''); - const [selectedTags, setSelectedTags] = useState([]); - const [installingTemplateId, setInstallingTemplateId] = useState(null); - const [selectedTemplate, setSelectedTemplate] = useState(null); - const [showInstallDialog, setShowInstallDialog] = useState(false); - const [showMissingCredsDialog, setShowMissingCredsDialog] = useState(false); - const [missingCredentials, setMissingCredentials] = useState([]); - - const queryParams = useMemo(() => ({ - limit: 20, - offset: (page - 1) * 20, - search: searchQuery || undefined, - tags: selectedTags.length > 0 ? selectedTags.join(',') : undefined, - }), [page, searchQuery, selectedTags]); - - const { data: templates, isLoading, error } = useMarketplaceTemplates(queryParams); - console.log(templates); - const { data: userCredentials } = useUserCredentials(); - const installTemplateMutation = useInstallTemplate(); - - React.useEffect(() => { - setPage(1); - }, [searchQuery, selectedTags]); - - const handleInstallClick = (template: AgentTemplate) => { - setSelectedTemplate(template); - setShowInstallDialog(true); - }; - - const handleInstall = async (templateId: string, instanceName?: string) => { - setInstallingTemplateId(templateId); - try { - const result = await installTemplateMutation.mutateAsync({ - template_id: templateId, - instance_name: instanceName - }); - - if (result.status === 'installed') { - toast.success('Agent installed successfully!'); - setShowInstallDialog(false); - } else if (result.status === 'credentials_required') { - setMissingCredentials(result.missing_credentials || []); - setShowInstallDialog(false); - setShowMissingCredsDialog(true); - } - } catch (error: any) { - toast.error(error.message || 'Failed to install agent'); - } finally { - setInstallingTemplateId(null); - } - }; - - const handleTagFilter = (tag: string) => { - setSelectedTags(prev => - prev.includes(tag) - ? prev.filter(t => t !== tag) - : [...prev, tag] - ); - }; - - const getTemplateStyling = (template: AgentTemplate) => { - if (template.avatar && template.avatar_color) { - return { - avatar: template.avatar, - color: template.avatar_color, - }; - } - return getAgentAvatar(template.template_id); - }; - - const allTags = React.useMemo(() => { - const tags = new Set(); - templates?.forEach(template => { - template.tags?.forEach(tag => tags.add(tag)); - }); - return Array.from(tags); - }, [templates]); - - const getUserCredentialNames = () => { - return new Set(userCredentials?.map(cred => cred.mcp_qualified_name) || []); - }; - - const getTemplateCredentialStatus = (template: AgentTemplate) => { - const userCredNames = getUserCredentialNames(); - const requiredCreds = template.mcp_requirements.map(req => req.qualified_name); - const missingCreds = requiredCreds.filter(cred => !userCredNames.has(cred)); - - return { - hasAllCredentials: missingCreds.length === 0, - missingCount: missingCreds.length, - totalRequired: requiredCreds.length - }; - }; - - if (error) { - return ( -
- - - Failed to load marketplace templates. Please try again later. - - -
- ); - } - - return ( -
-
-
-
-
-

- Secure Agent Marketplace -

- - - Secure - -
- -
-
-

- Discover and install secure AI agent templates. Your credentials stay private and encrypted. -

-
- - - - - These agent templates use your own encrypted credentials. No API keys are shared or exposed. - - Manage your credentials → - - - -
- -
-
- - setSearchQuery(e.target.value)} - className="pl-10" - /> -
-
- - {allTags.length > 0 && ( -
-

Filter by tags:

-
- {allTags.map(tag => ( - handleTagFilter(tag)} - > - - {tag} - - ))} -
-
- )} - -
- {isLoading ? ( - "Loading templates..." - ) : ( - `${templates?.length || 0} template${templates?.length !== 1 ? 's' : ''} found` - )} -
- - {isLoading ? ( -
- {Array.from({ length: 8 }).map((_, i) => ( -
- -
- -
- - -
- -
-
- ))} -
- ) : templates?.length === 0 ? ( -
-

- {searchQuery || selectedTags.length > 0 - ? "No templates found matching your criteria. Try adjusting your search or filters." - : "No agent templates are currently available in the marketplace."} -

-
- ) : ( -
- {templates?.map((template) => { - const { avatar, color } = getTemplateStyling(template); - const credentialStatus = getTemplateCredentialStatus(template); - - return ( -
-
-
- {avatar} -
-
-
- - {template.download_count || 0} -
-
-
- - - Secure - -
-
- -
-
-

- {template.name} -

-
-

- {template.description || 'No description available'} -

- - {template.mcp_requirements.length > 0 && ( -
-
- - MCP Services ({template.mcp_requirements.length}) - - {credentialStatus.hasAllCredentials ? ( - - ) : ( - - )} -
-
- {template.mcp_requirements.slice(0, 2).map(req => ( - - {req.display_name} - - ))} - {template.mcp_requirements.length > 2 && ( - - +{template.mcp_requirements.length - 2} - - )} -
-
- )} - - {template.tags && template.tags.length > 0 && ( -
- {template.tags.slice(0, 2).map(tag => ( - - {tag} - - ))} - {template.tags.length > 2 && ( - - +{template.tags.length - 2} - - )} -
- )} - -
-
- - By {template.creator_name || 'Anonymous'} -
- {template.marketplace_published_at && ( -
- - {new Date(template.marketplace_published_at).toLocaleDateString()} -
- )} -
- - -
-
- ); - })} -
- )} - - - - -
-
- ); -} \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/settings/credentials/_components/enhanced-add-credential-dialog.tsx b/frontend/src/app/(dashboard)/settings/credentials/_components/enhanced-add-credential-dialog.tsx index 1a590971..bfea5beb 100644 --- a/frontend/src/app/(dashboard)/settings/credentials/_components/enhanced-add-credential-dialog.tsx +++ b/frontend/src/app/(dashboard)/settings/credentials/_components/enhanced-add-credential-dialog.tsx @@ -36,7 +36,7 @@ const MCPServerCard: React.FC = ({ server, onClick }) => { {`${displayName} { e.currentTarget.style.display = 'none'; }} @@ -83,14 +83,14 @@ const CategorySidebar: React.FC = ({ categorizedServers }) => { return ( -
+

Categories

))}
diff --git a/frontend/src/app/(dashboard)/settings/credentials/page.tsx b/frontend/src/app/(dashboard)/settings/credentials/page.tsx index 97f1691b..05b003af 100644 --- a/frontend/src/app/(dashboard)/settings/credentials/page.tsx +++ b/frontend/src/app/(dashboard)/settings/credentials/page.tsx @@ -16,10 +16,6 @@ import { } from '@/hooks/react-query/secure-mcp/use-secure-mcp'; import { EnhancedAddCredentialDialog } from './_components/enhanced-add-credential-dialog'; - - - - interface CredentialCardProps { credential: MCPCredential; onTest: (qualifiedName: string) => void; @@ -39,67 +35,45 @@ const CredentialCard: React.FC = ({ const isDeleting = isDeletingId === credential.mcp_qualified_name; return ( - - + +
-
+
- -

{credential.display_name}

+
+ +
+

{credential.display_name}

-

+

{credential.mcp_qualified_name}

{credential.config_keys.map((key) => ( - + {key} ))}
{credential.last_used_at && ( -

- Last used: {new Date(credential.last_used_at).toLocaleDateString()} +

+ Last used {new Date(credential.last_used_at).toLocaleDateString()}

)}
-
+
-
@@ -149,10 +123,10 @@ export default function CredentialsPage() { if (error) { return ( -
- +
+ - + Failed to load credentials. Please try again later. @@ -161,44 +135,48 @@ export default function CredentialsPage() { } return ( -
-
-
+
+
+
-
-

+
+

MCP Credentials

-

+

Manage your encrypted API credentials for MCP servers

-

- - - - All credentials are encrypted and stored securely. You only need to set up each MCP service once, - and your credentials will be automatically used when installing agents that require them. + + + + All credentials are encrypted and stored securely. Set up each MCP service once, + and credentials will be automatically used when installing agents.
{isLoading ? ( -
+
{Array.from({ length: 3 }).map((_, i) => ( - - -
-
-
-
-
-
+ + +
+
+
+
+
+
@@ -206,21 +184,27 @@ export default function CredentialsPage() { ))}
) : credentials?.length === 0 ? ( - - - -

No credentials configured

-

- Add your first MCP credential to start using secure agents from the marketplace + + +

+ +
+

No credentials configured

+

+ Add your first MCP credential to start using secure agents

-
) : ( -
+
{credentials?.map((credential) => ( )} {marketplaceEnabled && ( - <> - - - - - Marketplace - - New - - - - - - - - - Secure Templates - - Secure - - - - - + + + + + Marketplace + + New + + + + )} )}