UNPKG

@mcp-shark/mcp-shark

Version:

Aggregate multiple Model Context Protocol (MCP) servers into a single unified interface with a powerful monitoring UI. Prov deep visibility into every request and response.

241 lines (237 loc) 6.69 kB
import { colors, fonts, withOpacity } from '../../theme'; import { formatRelativeTime, formatDateTime, getSourceDest, getEndpoint, } from '../../utils/requestUtils.js'; import { IconChevronDown } from '@tabler/icons-react'; const ChevronDown = ({ size = 12, rotated = false }) => ( <IconChevronDown size={size} stroke={1.5} style={{ display: 'inline-block', verticalAlign: 'middle', transform: rotated ? 'rotate(-90deg)' : 'rotate(0deg)', transition: 'transform 0.2s ease', }} /> ); export default function RequestRowMain({ request, response, selected, firstRequestTime, onSelect, isExpanded, onToggleExpand, isUnpaired, }) { const isSelected = selected?.frame_number === request.frame_number; const { source, dest } = getSourceDest(request); const relativeTime = formatRelativeTime(request.timestamp_iso, firstRequestTime); const hasResponse = !!response; return ( <> <tr onClick={() => onSelect(request)} style={{ cursor: 'pointer', background: isSelected ? colors.bgSelected : isUnpaired ? colors.bgUnpaired : colors.bgSecondary, borderBottom: `1px solid ${colors.borderLight}`, fontFamily: fonts.body, transition: 'background-color 0.15s ease', opacity: isUnpaired ? 0.85 : 1, }} onMouseEnter={(e) => { if (!isSelected) { e.currentTarget.style.background = isUnpaired ? withOpacity(colors.accentOrange, 0.1) : colors.bgCard; } }} onMouseLeave={(e) => { if (!isSelected) { e.currentTarget.style.background = isUnpaired ? colors.bgUnpaired : colors.bgSecondary; } }} > <td style={{ padding: '16px', borderRight: `1px solid ${colors.borderLight}`, color: colors.textPrimary, textAlign: 'right', fontFamily: fonts.mono, fontSize: '12px', }} > <div style={{ display: 'flex', alignItems: 'center', gap: '8px', justifyContent: 'flex-end', }} > {hasResponse && ( <button onClick={(e) => { e.stopPropagation(); onToggleExpand(); }} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', display: 'flex', alignItems: 'center', color: colors.textSecondary, }} onMouseEnter={(e) => { e.currentTarget.style.color = colors.textPrimary; }} onMouseLeave={(e) => { e.currentTarget.style.color = colors.textSecondary; }} > <ChevronDown size={14} rotated={!isExpanded} /> </button> )} <span style={{ color: isUnpaired ? colors.accentOrange : colors.accentBlue, fontWeight: '500', }} > #{request.frame_number} {isUnpaired && ( <span style={{ fontSize: '10px', color: colors.textSecondary, marginLeft: '4px', fontStyle: 'italic', }} > (no response) </span> )} </span> </div> </td> <td style={{ padding: '16px', borderRight: `1px solid ${colors.borderLight}`, color: colors.textPrimary, fontFamily: fonts.mono, fontSize: '12px', }} > {relativeTime} </td> <td style={{ padding: '16px', borderRight: `1px solid ${colors.borderLight}`, color: colors.textPrimary, fontFamily: fonts.body, fontSize: '12px', }} > {formatDateTime(request.timestamp_iso)} </td> <td style={{ padding: '16px', borderRight: `1px solid ${colors.borderLight}`, color: colors.accentBlue, fontFamily: fonts.mono, fontSize: '12px', fontWeight: '500', }} > {source} </td> <td style={{ padding: '16px', borderRight: `1px solid ${colors.borderLight}`, color: colors.accentBlue, fontFamily: fonts.mono, fontSize: '12px', fontWeight: '500', }} > {dest} </td> <td style={{ padding: '16px', borderRight: `1px solid ${colors.borderLight}`, color: colors.textPrimary, fontFamily: fonts.mono, fontSize: '12px', fontWeight: '500', }} > {request.protocol || 'HTTP'} </td> <td style={{ padding: '16px', borderRight: `1px solid ${colors.borderLight}`, color: colors.textPrimary, fontFamily: fonts.mono, fontSize: '12px', fontWeight: '500', }} > <div style={{ color: colors.accentBlue }}>{request.method || 'REQ'}</div> </td> <td style={{ padding: '16px', borderRight: `1px solid ${colors.borderLight}`, fontFamily: fonts.mono, fontSize: '12px', fontWeight: '500', }} > {hasResponse && ( <div style={{ color: response.status_code >= 400 ? colors.error : response.status_code >= 300 ? colors.warning : colors.success, fontWeight: '600', }} > {response.status_code || '-'} </div> )} </td> <td style={{ padding: '16px', color: colors.textPrimary, fontFamily: fonts.mono, fontSize: '12px', }} > {getEndpoint(request)} </td> </tr> </> ); }