From 787410a7e0316ecf99232632b477fcb7daa3b28b Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Thu, 4 Dec 2025 16:20:47 +1000 Subject: [PATCH] feat: add broken tools page at /tool/broken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create dedicated page to display all tools with failed health checks. Features: - Lists all tools with importHealth='BROKEN' OR executionHealth='BROKEN' - Displays health status badges for both import and execution - Shows error messages in code blocks for debugging - Includes last checked timestamp - Links to tool detail pages for manual recheck - Shows empty state with checkmark when all tools are healthy - Warning banner showing total broken tool count UI Components: - Card layout with red borders for broken tools - Health status icons (check/x) for visual status - Category badges and version info - Direct links to tool detail pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/src/app/tool/broken/page.tsx | 265 ++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 apps/web/src/app/tool/broken/page.tsx diff --git a/apps/web/src/app/tool/broken/page.tsx b/apps/web/src/app/tool/broken/page.tsx new file mode 100644 index 0000000..0e228bd --- /dev/null +++ b/apps/web/src/app/tool/broken/page.tsx @@ -0,0 +1,265 @@ +'use client'; + +import { Badge } from '@tpmjs/ui/Badge/Badge'; +import { Button } from '@tpmjs/ui/Button/Button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@tpmjs/ui/Card/Card'; +import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock'; +import { Container } from '@tpmjs/ui/Container/Container'; +import { Icon } from '@tpmjs/ui/Icon/Icon'; +import Link from 'next/link'; +import { useEffect, useState } from 'react'; +import { AppHeader } from '~/components/AppHeader'; + +interface BrokenTool { + id: string; + exportName: string; + description: string; + importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN'; + executionHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN'; + healthCheckError: string | null; + lastHealthCheck: string | null; + package: { + npmPackageName: string; + npmVersion: string; + category: string; + isOfficial: boolean; + }; +} + +/** + * Broken Tools Page + * + * Displays all tools with broken health status (importHealth='BROKEN' OR executionHealth='BROKEN') + */ +export default function BrokenToolsPage(): React.ReactElement { + const [tools, setTools] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch broken tools from API + useEffect(() => { + const fetchBrokenTools = async () => { + try { + setLoading(true); + const response = await fetch('/api/tools/broken'); + const data = await response.json(); + + if (data.success) { + setTools(data.data); + setError(null); + } else { + setError(data.error || 'Failed to fetch broken tools'); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + } finally { + setLoading(false); + } + }; + + fetchBrokenTools(); + }, []); + + return ( +
+ + + {/* Main content */} + + {/* Page header */} +
+
+ ⚠️ +

Broken Tools

+
+

+ Tools that are currently failing health checks. These tools may not work correctly until + the underlying issues are resolved. +

+
+ + {/* Loading state */} + {loading && ( +
Loading broken tools...
+ )} + + {/* Error state */} + {error &&
Error: {error}
} + + {/* Empty state - All healthy! */} + {!loading && !error && tools.length === 0 && ( + + +
+ +
+
+

All Tools are Healthy!

+

+ No broken tools detected. All tools are passing health checks. +

+
+ + + +
+
+ )} + + {/* Broken tools grid */} + {!loading && !error && tools.length > 0 && ( + <> +
+
+ ⚠️ +
+

+ {tools.length} {tools.length === 1 ? 'tool is' : 'tools are'} currently broken +

+

+ These tools are experiencing import or execution failures. Click on a tool to + see details and manually trigger a health recheck. +

+
+
+
+ +
+ {tools.map((tool) => { + const toolUrl = `/tool/${tool.package.npmPackageName}/${tool.exportName}`; + const lastCheckedDate = tool.lastHealthCheck + ? new Date(tool.lastHealthCheck) + : null; + + return ( + + +
+
+ + {tool.exportName !== 'default' + ? tool.exportName + : tool.package.npmPackageName} + +
+ {tool.package.npmPackageName} +
+
+
+ {tool.description} +
+ + + {/* Category badge and version */} +
+ + {tool.package.category} + + + v{tool.package.npmVersion} + + {tool.package.isOfficial && ( + + Official + + )} +
+ + {/* Health status badges */} +
+
+ Health Status: +
+
+ {/* Import health */} +
+ Import: + {tool.importHealth === 'BROKEN' ? ( + + + Broken + + ) : tool.importHealth === 'HEALTHY' ? ( + + + Healthy + + ) : ( + + Unknown + + )} +
+ + {/* Execution health */} +
+ + Execution: + + {tool.executionHealth === 'BROKEN' ? ( + + + Broken + + ) : tool.executionHealth === 'HEALTHY' ? ( + + + Healthy + + ) : ( + + Unknown + + )} +
+
+
+ + {/* Error message */} + {tool.healthCheckError && ( +
+
+ Error: +
+ +
+ )} + + {/* Last checked timestamp */} + {lastCheckedDate && ( +
+ Last checked: {lastCheckedDate.toLocaleString()} +
+ )} +
+ + + + + + +
+ ); + })} +
+ + )} +
+
+ ); +}