diff --git a/frontend/src/components/panels/DetailPanel.tsx b/frontend/src/components/panels/DetailPanel.tsx
index c238dad..71cbe7d 100644
--- a/frontend/src/components/panels/DetailPanel.tsx
+++ b/frontend/src/components/panels/DetailPanel.tsx
@@ -1,8 +1,7 @@
-import { X, Edit, Trash2 } from 'lucide-react'
-import { Badge } from '@/components/ui/badge'
+import { X, Edit, Trash2, ExternalLink } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { useCanvasStore } from '@/stores/canvasStore'
-import { NODE_TYPE_LABELS, STATUS_COLORS } from '@/types'
+import { NODE_TYPE_LABELS, STATUS_COLORS, type ServiceInfo } from '@/types'
interface DetailPanelProps {
onEdit: (id: string) => void
@@ -64,16 +63,10 @@ export function DetailPanel({ onEdit }: DetailPanelProps) {
{/* Services */}
{data.services.length > 0 && (
-
Services
-
+
Services ({data.services.length})
+
{data.services.map((svc) => (
-
- {svc.port}/{svc.protocol} {svc.service_name}
-
+
))}
@@ -123,3 +116,59 @@ function DetailRow({ label, value, mono }: { label: string; value: string; mono?
)
}
+
+const CATEGORY_COLORS: Record = {
+ web: '#00d4ff',
+ database: '#a855f7',
+ monitoring: '#39d353',
+ storage: '#e3b341',
+ security: '#f85149',
+ remote: '#8b949e',
+}
+
+function getServiceUrl(svc: ServiceInfo, ip?: string): string | null {
+ if (!ip) return null
+ const name = svc.service_name.toLowerCase()
+ const isHttps = name.includes('https') || name.includes('ssl') || svc.port === 443 || svc.port === 8443
+ const isHttp = name.includes('http') || [80, 8080, 8000, 3000, 8888, 9000, 8090, 7080].includes(svc.port)
+ if (isHttps) return `https://${ip}:${svc.port}`
+ if (isHttp) return `http://${ip}:${svc.port}`
+ return null
+}
+
+function ServiceBadge({ svc, ip }: { svc: ServiceInfo; ip?: string }) {
+ const url = getServiceUrl(svc, ip)
+ const color = CATEGORY_COLORS[svc.category ?? ''] ?? '#8b949e'
+
+ const inner = (
+
+
+
+ {svc.service_name}
+
+
+ {svc.port}/{svc.protocol}
+ {url && }
+
+
+ )
+
+ if (url) {
+ return (
+
+ {inner}
+
+ )
+ }
+ return inner
+}