From f32c32f6a56fa2fd45d8e4b467a76507aabe7531 Mon Sep 17 00:00:00 2001 From: findthelorax Date: Sun, 19 Apr 2026 21:31:33 -0400 Subject: [PATCH 01/10] feature: added a toggle to show the services on a node --- .../canvas/__tests__/BaseNode.test.tsx | 52 ++++++++++++++++++- .../src/components/canvas/nodes/BaseNode.tsx | 51 +++++++++++++++++- frontend/src/components/modals/NodeModal.tsx | 32 ++++++++++-- .../modals/__tests__/NodeModal.test.tsx | 25 ++++++++- frontend/src/types/index.ts | 1 + 5 files changed, 152 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/canvas/__tests__/BaseNode.test.tsx b/frontend/src/components/canvas/__tests__/BaseNode.test.tsx index ae5591f..10c6107 100644 --- a/frontend/src/components/canvas/__tests__/BaseNode.test.tsx +++ b/frontend/src/components/canvas/__tests__/BaseNode.test.tsx @@ -20,7 +20,9 @@ vi.mock('@/stores/themeStore', () => ({ })) vi.mock('@/stores/canvasStore', () => ({ - useCanvasStore: (sel: (s: { hideIp: boolean }) => unknown) => sel({ hideIp: false }), + useCanvasStore: (sel: (s: { hideIp: boolean }) => unknown) => sel({ + hideIp: false, + }), })) vi.mock('@/utils/themes', () => ({ @@ -49,6 +51,7 @@ vi.mock('@/utils/nodeIcons', () => ({ vi.mock('@/utils/maskIp', () => ({ maskIp: (ip: string) => ip, splitIps: (ip: string) => ip ? ip.split(',').map((s: string) => s.trim()).filter(Boolean) : [], + primaryIp: (ip: string) => ip ? ip.split(',')[0].trim() : '', })) vi.mock('@/utils/propertyIcons', () => ({ @@ -61,7 +64,9 @@ vi.mock('@/utils/handleUtils', () => ({ clampBottomHandles: (n: unknown) => typeof n === 'number' ? n : 1, })) -beforeEach(() => { mockZoom = 1 }) +beforeEach(() => { + mockZoom = 1 +}) function makeNode(data: Partial): Node { return { @@ -169,6 +174,49 @@ describe('BaseNode — properties rendering', () => { }) }) +describe('BaseNode — services visibility toggle', () => { + it('does not render service toggle button on the node', () => { + renderBaseNode({ services: [{ service_name: 'nginx', port: 80, protocol: 'tcp' }] }) + expect(screen.queryByTitle('Show services')).toBeNull() + }) + + it('renders service rows when services are toggled on', () => { + renderBaseNode({ + ip: '192.168.1.10', + custom_colors: { show_services: true }, + services: [ + { service_name: 'nginx', port: 80, protocol: 'tcp' }, + { service_name: 'ssh', port: 22, protocol: 'tcp' }, + ], + }) + + expect(screen.getByText('nginx')).toBeDefined() + expect(screen.getByText('80')).toBeDefined() + expect(screen.getByText('ssh')).toBeDefined() + }) + + it('renders clickable service links for web services', () => { + renderBaseNode({ + ip: '192.168.1.10', + custom_colors: { show_services: true }, + services: [{ service_name: 'nginx', port: 80, protocol: 'tcp' }], + }) + + const link = screen.getByRole('link', { name: /nginx/i }) as HTMLAnchorElement + expect(link.getAttribute('href')).toBe('http://192.168.1.10:80') + }) + + it('keeps non-web services as non-clickable rows', () => { + renderBaseNode({ + ip: '192.168.1.10', + custom_colors: { show_services: true }, + services: [{ service_name: 'ssh', port: 22, protocol: 'tcp' }], + }) + + expect(screen.queryByRole('link', { name: /ssh/i })).toBeNull() + }) +}) + describe('BaseNode — legacy hardware fallback', () => { it('renders legacy hardware when properties is undefined and show_hardware is true', () => { renderBaseNode({ diff --git a/frontend/src/components/canvas/nodes/BaseNode.tsx b/frontend/src/components/canvas/nodes/BaseNode.tsx index c239452..177e4cb 100644 --- a/frontend/src/components/canvas/nodes/BaseNode.tsx +++ b/frontend/src/components/canvas/nodes/BaseNode.tsx @@ -1,6 +1,6 @@ import { createElement, useEffect, useMemo } from 'react' import { Handle, Position, NodeResizer, useUpdateNodeInternals, useViewport, type NodeProps, type Node } from '@xyflow/react' -import { Cpu, MemoryStick, HardDrive, type LucideIcon } from 'lucide-react' +import { Cpu, MemoryStick, HardDrive, ExternalLink, type LucideIcon } from 'lucide-react' import type { NodeData } from '@/types' import { resolveNodeColors } from '@/utils/nodeColors' import { resolveNodeIcon } from '@/utils/nodeIcons' @@ -8,8 +8,9 @@ import { resolvePropertyIcon } from '@/utils/propertyIcons' import { useThemeStore } from '@/stores/themeStore' import { THEMES } from '@/utils/themes' import { useCanvasStore } from '@/stores/canvasStore' -import { maskIp, splitIps } from '@/utils/maskIp' +import { maskIp, primaryIp, splitIps } from '@/utils/maskIp' import { bottomHandleId, bottomHandlePositions, clampBottomHandles } from '@/utils/handleUtils' +import { getServiceUrl } from '@/utils/serviceUrl' interface BaseNodeProps extends NodeProps> { icon: LucideIcon @@ -35,6 +36,9 @@ export function BaseNode({ id, data, selected, icon: typeIcon, width, height }: const colors = resolveNodeColors(data, activeTheme) const statusColor = theme.colors.statusColors[data.status] const isOnline = data.status === 'online' + const services = data.services ?? [] + const showServices = data.custom_colors?.show_services === true + const serviceHost = data.ip ? primaryIp(data.ip) : data.hostname // Properties: prefer new system; fall back to legacy hardware fields for unmigrated nodes const visibleProperties = data.properties?.filter((p) => p.visible) ?? null @@ -117,6 +121,7 @@ export function BaseNode({ id, data, selected, icon: typeIcon, width, height }: ))} + {/* Properties section (new system) */} @@ -138,6 +143,48 @@ export function BaseNode({ id, data, selected, icon: typeIcon, width, height }: )} + {showServices && services.length > 0 && ( + <> +
+
+ {services.map((svc, idx) => { + const url = getServiceUrl(svc, serviceHost) + const row = ( +
+ {svc.service_name} + + {svc.port} + {url && } + +
+ ) + + if (!url) return
{row}
+ + return ( + e.stopPropagation()} + > + {row} + + ) + })} +
+ + )} + {/* Legacy hardware section — fallback for nodes not yet migrated */} {showLegacyHardware && ( <> diff --git a/frontend/src/components/modals/NodeModal.tsx b/frontend/src/components/modals/NodeModal.tsx index c1666ba..13980d8 100644 --- a/frontend/src/components/modals/NodeModal.tsx +++ b/frontend/src/components/modals/NodeModal.tsx @@ -104,7 +104,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' {NODE_TYPE_GROUPS.map((group, i) => ( - {i > 0 && } + {i > 0 && } {group.label} @@ -303,11 +303,10 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' +
+ )} + {/* Appearance */}
diff --git a/frontend/src/components/modals/__tests__/NodeModal.test.tsx b/frontend/src/components/modals/__tests__/NodeModal.test.tsx index 147f14d..e2a3c20 100644 --- a/frontend/src/components/modals/__tests__/NodeModal.test.tsx +++ b/frontend/src/components/modals/__tests__/NodeModal.test.tsx @@ -273,12 +273,33 @@ describe('NodeModal', () => { it('toggles container_mode on click', () => { const { onSubmit } = renderModal({ initial: { ...BASE, type: 'proxmox', container_mode: true } }) - fireEvent.click(screen.getByRole('switch')) + fireEvent.click(screen.getByRole('switch', { name: 'Container Mode' })) fireEvent.click(screen.getByRole('button', { name: 'Add' })) expect((onSubmit.mock.calls[0][0] as Partial).container_mode).toBe(false) }) - // ── Parent container ────────────────────────────────────────────────── + // ── Show services toggle (modal-only) ─────────────────────────────── + + it('shows Show Services toggle for regular nodes', () => { + renderModal({ initial: BASE }) + expect(screen.getByText('Show Services')).toBeDefined() + expect(screen.getByRole('switch', { name: 'Show Services' })).toBeDefined() + }) + + it('hides Show Services toggle for groupRect', () => { + renderModal({ initial: { ...BASE, type: 'groupRect' } }) + expect(screen.queryByText('Show Services')).toBeNull() + }) + + it('submits custom_colors.show_services=true when toggled on', () => { + const { onSubmit } = renderModal({ initial: BASE }) + fireEvent.click(screen.getByRole('switch', { name: 'Show Services' })) + fireEvent.click(screen.getByRole('button', { name: 'Add' })) + const data = onSubmit.mock.calls[0][0] as Partial + expect(data.custom_colors?.show_services).toBe(true) + }) + + // ── Parent Proxmox (vm / lxc only) ─────────────────────────────────── const parentContainerVisibleTypes = ['proxmox', 'vm', 'lxc', 'docker_host', 'isp', 'router', 'switch', 'server', 'nas', 'ap', 'printer', 'iot', 'camera', 'cpl', 'computer', 'generic'] as const const parentContainerHiddenTypes = ['groupRect', 'group'] as const diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 76291a8..59f687e 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -82,6 +82,7 @@ export interface NodeData extends Record { border?: string background?: string icon?: string + show_services?: boolean // Group rectangle extras (type === 'groupRect') text_color?: string text_position?: TextPosition From 4a048d0ab57148a7b952d8276baab2c57e2653ff Mon Sep 17 00:00:00 2001 From: findthelorax Date: Sun, 19 Apr 2026 21:36:22 -0400 Subject: [PATCH 02/10] fix: styling --- frontend/src/components/modals/NodeModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/modals/NodeModal.tsx b/frontend/src/components/modals/NodeModal.tsx index 13980d8..21d14a3 100644 --- a/frontend/src/components/modals/NodeModal.tsx +++ b/frontend/src/components/modals/NodeModal.tsx @@ -61,6 +61,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' const [iconSearch, setIconSearch] = useState('') const [iconPickerOpen, setIconPickerOpen] = useState(false) const [labelError, setLabelError] = useState(false) + const resolvedNodeColors = resolveNodeColors({ type: form.type ?? 'generic', custom_colors: form.custom_colors }) const set = (key: keyof NodeData, value: unknown) => setForm((f) => ({ ...f, [key]: value })) @@ -334,7 +335,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' show_services: !(form.custom_colors?.show_services === true), })} className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus:outline-none" - style={{ background: form.custom_colors?.show_services === true ? '#00d4ff' : '#30363d' }} + style={{ background: form.custom_colors?.show_services === true ? resolvedNodeColors.icon : '#30363d' }} > Date: Sun, 19 Apr 2026 21:52:18 -0400 Subject: [PATCH 03/10] fix: external link icon set to opacity 0 when no URL --- frontend/src/components/canvas/nodes/BaseNode.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/canvas/nodes/BaseNode.tsx b/frontend/src/components/canvas/nodes/BaseNode.tsx index 177e4cb..05db528 100644 --- a/frontend/src/components/canvas/nodes/BaseNode.tsx +++ b/frontend/src/components/canvas/nodes/BaseNode.tsx @@ -160,7 +160,7 @@ export function BaseNode({ id, data, selected, icon: typeIcon, width, height }: {svc.service_name} {svc.port} - {url && } +
) From 340bd150992c8226333e796266d0b9e743bbbbd5 Mon Sep 17 00:00:00 2001 From: findthelorax Date: Sun, 19 Apr 2026 21:55:47 -0400 Subject: [PATCH 04/10] fix: external link icon on the detail panel for non URL services --- .../src/components/panels/DetailPanel.tsx | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/panels/DetailPanel.tsx b/frontend/src/components/panels/DetailPanel.tsx index b53a8ad..1152112 100644 --- a/frontend/src/components/panels/DetailPanel.tsx +++ b/frontend/src/components/panels/DetailPanel.tsx @@ -662,38 +662,41 @@ const CATEGORY_COLORS: Record = { function ServiceBadge({ svc, host, onEdit, onRemove }: { svc: ServiceInfo; host?: string; onEdit: () => void; onRemove: () => void }) { const url = getServiceUrl(svc, host) const color = CATEGORY_COLORS[svc.category ?? ''] ?? '#8b949e' + const hasPort = svc.port != null - const portLabel = hasPort ? String(svc.port) : '' + const portLabel = hasPort ? String(svc.port) : 'host' const pathLabel = svc.path?.trim() ? svc.path.trim() : '' return (
- - {url ? ( - e.stopPropagation()} - > - {svc.service_name} - - ) : ( - - {svc.service_name} - - )} -
+
+ + + {url ? ( + e.stopPropagation()} + > + {svc.service_name} + + ) : ( + + {svc.service_name} + + )} + {pathLabel && ( @@ -710,34 +713,38 @@ function ServiceBadge({ svc, host, onEdit, onRemove }: { svc: ServiceInfo; host? )} - {hasPort && ( - {portLabel}/{svc.protocol} - )} +
+ +
+ + {portLabel}/{svc.protocol} + + {url ? ( e.stopPropagation()} > ) : ( - + )} + +
))}
-
{/* Properties section (new system) */} From b16e81cf29a417da5f0a19a2f1786f5a1772eb5b Mon Sep 17 00:00:00 2001 From: findthelorax Date: Tue, 21 Apr 2026 10:25:15 -0400 Subject: [PATCH 06/10] fixed some styling inconsistencies, removed show services toggle from affecting custom colors --- frontend/src/components/modals/NodeModal.tsx | 51 ++++++++++++++----- .../modals/__tests__/NodeModal.test.tsx | 12 +++++ 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/modals/NodeModal.tsx b/frontend/src/components/modals/NodeModal.tsx index 21d14a3..4d9b18a 100644 --- a/frontend/src/components/modals/NodeModal.tsx +++ b/frontend/src/components/modals/NodeModal.tsx @@ -62,6 +62,12 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' const [iconPickerOpen, setIconPickerOpen] = useState(false) const [labelError, setLabelError] = useState(false) const resolvedNodeColors = resolveNodeColors({ type: form.type ?? 'generic', custom_colors: form.custom_colors }) + const showServicesEnabled = form.custom_colors?.show_services === true + const hasAppearanceOverrides = Boolean( + form.custom_colors?.border + || form.custom_colors?.background + || form.custom_colors?.icon + ) const set = (key: keyof NodeData, value: unknown) => setForm((f) => ({ ...f, [key]: value })) @@ -299,8 +305,11 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
- Allow other nodes to nest inside this node + + Allow other nodes to nest inside this node +
+
@@ -329,17 +340,17 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' type="button" role="switch" aria-label="Show Services" - aria-checked={form.custom_colors?.show_services === true} + aria-checked={showServicesEnabled} onClick={() => set('custom_colors', { ...form.custom_colors, - show_services: !(form.custom_colors?.show_services === true), + show_services: !showServicesEnabled, })} - className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus:outline-none" - style={{ background: form.custom_colors?.show_services === true ? resolvedNodeColors.icon : '#30363d' }} + className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full p-0.5 transition-colors focus:outline-none" + style={{ background: showServicesEnabled ? resolvedNodeColors.icon : '#30363d' }} >
@@ -349,10 +360,20 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
- {form.custom_colors && ( + {hasAppearanceOverrides && (
- {!form.custom_colors && ( -

Using default colors for {NODE_TYPE_LABELS[form.type ?? 'generic']}. Click a swatch to customize.

- )} +
+ {!hasAppearanceOverrides && ( +

Using default colors for {NODE_TYPE_LABELS[form.type ?? 'generic']}. Click a swatch to customize.

+ )} +
{/* Bottom connection points (not for group containers) */} diff --git a/frontend/src/components/modals/__tests__/NodeModal.test.tsx b/frontend/src/components/modals/__tests__/NodeModal.test.tsx index e2a3c20..ef32c0d 100644 --- a/frontend/src/components/modals/__tests__/NodeModal.test.tsx +++ b/frontend/src/components/modals/__tests__/NodeModal.test.tsx @@ -299,6 +299,18 @@ describe('NodeModal', () => { expect(data.custom_colors?.show_services).toBe(true) }) + it('keeps default colors hint visible when Show Services is toggled on', () => { + renderModal({ initial: BASE }) + fireEvent.click(screen.getByRole('switch', { name: 'Show Services' })) + expect(screen.getByText(/Using default colors for/)).toBeDefined() + }) + + it('does not show Appearance reset when only Show Services is set', () => { + renderModal({ initial: BASE }) + fireEvent.click(screen.getByRole('switch', { name: 'Show Services' })) + expect(screen.queryByText('Reset to defaults')).toBeNull() + }) + // ── Parent Proxmox (vm / lxc only) ─────────────────────────────────── const parentContainerVisibleTypes = ['proxmox', 'vm', 'lxc', 'docker_host', 'isp', 'router', 'switch', 'server', 'nas', 'ap', 'printer', 'iot', 'camera', 'cpl', 'computer', 'generic'] as const From c165b8b0167a6c0a8bc6d1010e8459bfe2e05888 Mon Sep 17 00:00:00 2001 From: findthelorax Date: Thu, 23 Apr 2026 21:15:17 -0400 Subject: [PATCH 07/10] rebase with new main branch --- frontend/src/components/modals/NodeModal.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/modals/NodeModal.tsx b/frontend/src/components/modals/NodeModal.tsx index 4d9b18a..18bf616 100644 --- a/frontend/src/components/modals/NodeModal.tsx +++ b/frontend/src/components/modals/NodeModal.tsx @@ -111,7 +111,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' {NODE_TYPE_GROUPS.map((group, i) => ( - {i > 0 && } + {i > 0 && } {group.label} @@ -320,9 +320,9 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' style={{ background: form.container_mode ? '#ff6e00' : '#30363d' }} > @@ -331,7 +331,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' {/* Service visibility */} {form.type !== 'groupRect' && form.type !== 'group' && ( -
+
Display discovered services on the node card @@ -345,11 +345,11 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' ...form.custom_colors, show_services: !showServicesEnabled, })} - className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full p-0.5 transition-colors focus:outline-none" + className="relative inline-flex h-5 w-9 mt-1 shrink-0 cursor-pointer rounded-full transition-colors focus:outline-none" style={{ background: showServicesEnabled ? resolvedNodeColors.icon : '#30363d' }} > From d9d4be9a53ccff8aa0dc7ae226a6cab3e37d534a Mon Sep 17 00:00:00 2001 From: findthelorax Date: Thu, 23 Apr 2026 22:22:07 -0400 Subject: [PATCH 08/10] services styling in node and detail panel --- .../src/components/canvas/nodes/BaseNode.tsx | 36 +++++++++++++++--- .../src/components/panels/DetailPanel.tsx | 37 +++++++------------ 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/canvas/nodes/BaseNode.tsx b/frontend/src/components/canvas/nodes/BaseNode.tsx index dbd9f77..2a3babd 100644 --- a/frontend/src/components/canvas/nodes/BaseNode.tsx +++ b/frontend/src/components/canvas/nodes/BaseNode.tsx @@ -156,11 +156,37 @@ export function BaseNode({ id, data, selected, icon: typeIcon, width, height }: color: theme.colors.nodeSubtextColor, }} > - {svc.service_name} - - {svc.port} - - + +
+ {/* LEFT: service name */} + + {svc.service_name} + + + {/* RIGHT: path + port */} +
+ {svc.path && ( + + {svc.path} + + )} + + + {svc.port} + + +
+
) diff --git a/frontend/src/components/panels/DetailPanel.tsx b/frontend/src/components/panels/DetailPanel.tsx index 1152112..46dab30 100644 --- a/frontend/src/components/panels/DetailPanel.tsx +++ b/frontend/src/components/panels/DetailPanel.tsx @@ -2,7 +2,7 @@ import { createElement, useState } from 'react' import { X, Edit, Trash2, ExternalLink, Plus, Pencil, Layers, Ungroup, Eye, EyeOff } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' -import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip' + import { useCanvasStore } from '@/stores/canvasStore' import { NODE_TYPE_LABELS, STATUS_COLORS, type ServiceInfo, type NodeData, type NodeProperty } from '@/types' import { getServiceUrl } from '@/utils/serviceUrl' @@ -662,9 +662,6 @@ const CATEGORY_COLORS: Record = { function ServiceBadge({ svc, host, onEdit, onRemove }: { svc: ServiceInfo; host?: string; onEdit: () => void; onRemove: () => void }) { const url = getServiceUrl(svc, host) const color = CATEGORY_COLORS[svc.category ?? ''] ?? '#8b949e' - - const hasPort = svc.port != null - const portLabel = hasPort ? String(svc.port) : 'host' const pathLabel = svc.path?.trim() ? svc.path.trim() : '' return ( @@ -680,7 +677,7 @@ function ServiceBadge({ svc, host, onEdit, onRemove }: { svc: ServiceInfo; host? href={url} target="_blank" rel="noopener noreferrer" - className="font-medium truncate" + className="font-medium truncate min-w-0 flex-1" style={{ color }} title={svc.service_name} onClick={e => e.stopPropagation()} @@ -689,7 +686,7 @@ function ServiceBadge({ svc, host, onEdit, onRemove }: { svc: ServiceInfo; host? ) : ( @@ -698,27 +695,21 @@ function ServiceBadge({ svc, host, onEdit, onRemove }: { svc: ServiceInfo; host? )} {pathLabel && ( - - - - - {pathLabel} - - - {pathLabel} - - + + {pathLabel} + )}
- - {portLabel}/{svc.protocol} - + {svc.port != null && ( + + {svc.port}/{svc.protocol} + + )} {url ? ( Date: Mon, 4 May 2026 21:07:52 -0400 Subject: [PATCH 09/10] adjusted show services toggle for alignment --- frontend/src/components/modals/NodeModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/modals/NodeModal.tsx b/frontend/src/components/modals/NodeModal.tsx index 18bf616..f04abc7 100644 --- a/frontend/src/components/modals/NodeModal.tsx +++ b/frontend/src/components/modals/NodeModal.tsx @@ -349,7 +349,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' style={{ background: showServicesEnabled ? resolvedNodeColors.icon : '#30363d' }} > From 3cddcf5b81b3b8b77c6e4c10b6ac2f43b049b489 Mon Sep 17 00:00:00 2001 From: findthelorax Date: Mon, 4 May 2026 21:09:46 -0400 Subject: [PATCH 10/10] added cursor pointer to edit and delete buttons on services --- frontend/src/components/panels/DetailPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/panels/DetailPanel.tsx b/frontend/src/components/panels/DetailPanel.tsx index 46dab30..96d8cd4 100644 --- a/frontend/src/components/panels/DetailPanel.tsx +++ b/frontend/src/components/panels/DetailPanel.tsx @@ -727,7 +727,7 @@ function ServiceBadge({ svc, host, onEdit, onRemove }: { svc: ServiceInfo; host?