feat: add hide IP toggle in sidebar
- Add hideIp / toggleHideIp to canvasStore (UI-only, not persisted) - Add maskIp util: 192.168.1.115 → 192.168.XX.XX (non-IPv4 passthrough) - BaseNode reads hideIp from store and masks last two octets when active - Sidebar shows Eye/EyeOff toggle below Scan Network, highlights when active
This commit is contained in:
@@ -6,6 +6,8 @@ import { resolveNodeColors } from '@/utils/nodeColors'
|
||||
import { resolveNodeIcon } from '@/utils/nodeIcons'
|
||||
import { useThemeStore } from '@/stores/themeStore'
|
||||
import { THEMES } from '@/utils/themes'
|
||||
import { useCanvasStore } from '@/stores/canvasStore'
|
||||
import { maskIp } from '@/utils/maskIp'
|
||||
|
||||
interface BaseNodeProps extends NodeProps<Node<NodeData>> {
|
||||
icon: LucideIcon
|
||||
@@ -13,6 +15,7 @@ interface BaseNodeProps extends NodeProps<Node<NodeData>> {
|
||||
|
||||
export function BaseNode({ data, selected, icon: typeIcon }: BaseNodeProps) {
|
||||
const activeTheme = useThemeStore((s) => s.activeTheme)
|
||||
const hideIp = useCanvasStore((s) => s.hideIp)
|
||||
const theme = THEMES[activeTheme]
|
||||
|
||||
const resolvedIcon = resolveNodeIcon(typeIcon, data.custom_icon)
|
||||
@@ -70,7 +73,7 @@ export function BaseNode({ data, selected, icon: typeIcon }: BaseNodeProps) {
|
||||
style={{ color: theme.colors.nodeSubtextColor }}
|
||||
title={data.ip}
|
||||
>
|
||||
{data.ip}
|
||||
{hideIp ? maskIp(data.ip) : data.ip}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { Plus, Save, ScanLine, ChevronLeft, ChevronRight, LayoutDashboard, Clock, EyeOff, Trash2, RefreshCw, Loader2, Square } from 'lucide-react'
|
||||
import { Plus, Save, ScanLine, ChevronLeft, ChevronRight, LayoutDashboard, Clock, EyeOff, Trash2, RefreshCw, Loader2, Square, Eye } from 'lucide-react'
|
||||
import { Logo } from '@/components/ui/Logo'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useCanvasStore } from '@/stores/canvasStore'
|
||||
@@ -40,7 +40,7 @@ interface SidebarProps {
|
||||
export function Sidebar({ onAddNode, onAddGroupRect, onScan, onSave, onNodeApproved }: SidebarProps) {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [activeView, setActiveView] = useState<SidebarView>('canvas')
|
||||
const { nodes, hasUnsavedChanges } = useCanvasStore()
|
||||
const { nodes, hasUnsavedChanges, hideIp, toggleHideIp } = useCanvasStore()
|
||||
|
||||
const networkNodes = nodes.filter((n) => n.data.type !== 'groupRect')
|
||||
const onlineCount = networkNodes.filter((n) => n.data.status === 'online').length
|
||||
@@ -126,6 +126,13 @@ export function Sidebar({ onAddNode, onAddGroupRect, onScan, onSave, onNodeAppro
|
||||
<SidebarItem icon={Plus} label="Add Node" collapsed={collapsed} onClick={onAddNode} />
|
||||
<SidebarItem icon={Square} label="Add Rectangle" collapsed={collapsed} onClick={onAddGroupRect} />
|
||||
{!STANDALONE && <SidebarItem icon={ScanLine} label="Scan Network" collapsed={collapsed} onClick={handleScan} />}
|
||||
<SidebarItem
|
||||
icon={hideIp ? EyeOff : Eye}
|
||||
label={hideIp ? 'Show IPs' : 'Hide IPs'}
|
||||
collapsed={collapsed}
|
||||
onClick={toggleHideIp}
|
||||
active={hideIp}
|
||||
/>
|
||||
<SidebarItem
|
||||
icon={Save}
|
||||
label="Save Canvas"
|
||||
|
||||
@@ -35,6 +35,8 @@ interface CanvasState {
|
||||
markUnsaved: () => void
|
||||
loadCanvas: (nodes: Node<NodeData>[], edges: Edge<EdgeData>[]) => void
|
||||
notifyScanDeviceFound: () => void
|
||||
hideIp: boolean
|
||||
toggleHideIp: () => void
|
||||
}
|
||||
|
||||
export const useCanvasStore = create<CanvasState>((set) => ({
|
||||
@@ -43,6 +45,7 @@ export const useCanvasStore = create<CanvasState>((set) => ({
|
||||
hasUnsavedChanges: false,
|
||||
selectedNodeId: null,
|
||||
editingGroupRectId: null,
|
||||
hideIp: false,
|
||||
scanEventTs: 0,
|
||||
|
||||
onNodesChange: (changes) =>
|
||||
@@ -160,6 +163,9 @@ export const useCanvasStore = create<CanvasState>((set) => ({
|
||||
|
||||
notifyScanDeviceFound: () => set({ scanEventTs: Date.now() }),
|
||||
|
||||
hideIp: false,
|
||||
toggleHideIp: () => set((s) => ({ hideIp: !s.hideIp })),
|
||||
|
||||
loadCanvas: (nodes, edges) => {
|
||||
// React Flow requires parents before children in the array
|
||||
const parents = nodes.filter((n) => !n.parentId)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { maskIp } from '../maskIp'
|
||||
|
||||
describe('maskIp', () => {
|
||||
it('masks last two octets of a standard IPv4', () => {
|
||||
expect(maskIp('192.168.1.115')).toBe('192.168.XX.XX')
|
||||
})
|
||||
|
||||
it('masks any IPv4', () => {
|
||||
expect(maskIp('10.0.0.1')).toBe('10.0.XX.XX')
|
||||
expect(maskIp('172.16.254.1')).toBe('172.16.XX.XX')
|
||||
})
|
||||
|
||||
it('passes through non-IPv4 strings unchanged', () => {
|
||||
expect(maskIp('hostname')).toBe('hostname')
|
||||
expect(maskIp('fe80::1')).toBe('fe80::1')
|
||||
expect(maskIp('')).toBe('')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Mask the last two octets of an IPv4 address.
|
||||
* e.g. "192.168.1.115" → "192.168.XX.XX"
|
||||
* Non-IPv4 strings are returned unchanged.
|
||||
*/
|
||||
export function maskIp(ip: string): string {
|
||||
const parts = ip.split('.')
|
||||
if (parts.length === 4) return `${parts[0]}.${parts[1]}.XX.XX`
|
||||
return ip
|
||||
}
|
||||
Reference in New Issue
Block a user