Skip to content

Commit ca1e7a3

Browse files
committed
Enhance NetworkNode and NetworkView components with user data and improved search functionality
- Updated NetworkNode to conditionally display user information. - Modified NetworkView to include user data in device nodes and enhance tag handling. - Implemented a new helper function to retrieve device data by IP. - Improved search functionality to support filtering by user, tags, and IP addresses. - Updated search input placeholder with examples for better user guidance.
1 parent a0b406a commit ca1e7a3

File tree

2 files changed

+107
-18
lines changed

2 files changed

+107
-18
lines changed

frontend/src/components/NetworkNode.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,11 @@ const NetworkNode = memo<NodeProps>(({ data, selected }) => {
190190
</header>
191191

192192
{/* User/Identity section */}
193-
{(data as NetworkNodeData).user ? (
193+
{(data as NetworkNodeData).user && (
194194
<div className="flex items-center gap-1 mb-3 text-xs">
195195
<span className="text-indigo-600">👤</span>
196196
<span className="font-medium text-indigo-600 truncate">{(data as NetworkNodeData).user}</span>
197197
</div>
198-
) : processedData.deviceTags.length > 0 && (
199-
<div className="text-xs text-gray-600 mb-3 truncate">
200-
{processedData.deviceTags.slice(0, 2).map(tag => `tag:${tag}`).join(' ')}
201-
</div>
202198
)}
203199

204200
{/* IP Addresses section */}

frontend/src/pages/NetworkView.tsx

Lines changed: 106 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface TailscaleDevice {
1414
addresses: string[]
1515
os: string
1616
tags?: string[]
17+
user?: string
1718
lastSeen: string
1819
}
1920

@@ -27,6 +28,7 @@ interface NetworkNode {
2728
rxBytes: number
2829
connections: number
2930
tags: string[]
31+
user?: string
3032
isTailscale: boolean
3133
ips?: string[] // Track all IPs for merged devices
3234
incomingPorts: Set<number> // Ports this device receives traffic on
@@ -139,6 +141,11 @@ const getDeviceName = (ip: string, devices: TailscaleDevice[] = []): string => {
139141
return ip
140142
}
141143

144+
// Helper function to get device data from IP
145+
const getDeviceData = (ip: string, devices: TailscaleDevice[] = []): TailscaleDevice | null => {
146+
return devices.find(d => d.addresses.includes(ip)) || null
147+
}
148+
142149

143150
const NetworkView: React.FC = () => {
144151
const [selectedNode, setSelectedNode] = useState<NetworkNode | null>(null)
@@ -278,6 +285,13 @@ const NetworkView: React.FC = () => {
278285
const srcNodeId = srcDeviceName !== srcIP ? srcDeviceName : srcIP
279286
if (!nodeMap.has(srcNodeId)) {
280287
const isTailscale = categorizeIP(srcIP).includes('tailscale')
288+
const deviceData = getDeviceData(srcIP, devices)
289+
290+
// Combine IP-derived tags with device tags
291+
const ipTags = categorizeIP(srcIP)
292+
const deviceTags = deviceData?.tags || []
293+
const allTags = [...ipTags, ...deviceTags].filter((tag, index, arr) => arr.indexOf(tag) === index)
294+
281295
nodeMap.set(srcNodeId, {
282296
id: srcNodeId,
283297
ip: srcIP,
@@ -287,7 +301,8 @@ const NetworkView: React.FC = () => {
287301
txBytes: 0,
288302
rxBytes: 0,
289303
connections: 0,
290-
tags: categorizeIP(srcIP),
304+
tags: allTags,
305+
user: deviceData?.user,
291306
isTailscale,
292307
ips: [srcIP],
293308
incomingPorts: new Set<number>(),
@@ -301,11 +316,20 @@ const NetworkView: React.FC = () => {
301316
existingNode.ips = [...(existingNode.ips || []), srcIP]
302317
// Update tags to include IPv6 if this IP is IPv6
303318
const newTags = categorizeIP(srcIP)
304-
newTags.forEach(tag => {
319+
const deviceData = getDeviceData(srcIP, devices)
320+
const deviceTags = deviceData?.tags || []
321+
const combinedTags = [...newTags, ...deviceTags]
322+
323+
combinedTags.forEach(tag => {
305324
if (!existingNode.tags.includes(tag)) {
306325
existingNode.tags.push(tag)
307326
}
308327
})
328+
329+
// Update user if not already set
330+
if (!existingNode.user && deviceData?.user) {
331+
existingNode.user = deviceData.user
332+
}
309333
}
310334
}
311335

@@ -314,6 +338,13 @@ const NetworkView: React.FC = () => {
314338
const dstNodeId = dstDeviceName !== dstIP ? dstDeviceName : dstIP
315339
if (!nodeMap.has(dstNodeId)) {
316340
const isTailscale = categorizeIP(dstIP).includes('tailscale')
341+
const deviceData = getDeviceData(dstIP, devices)
342+
343+
// Combine IP-derived tags with device tags
344+
const ipTags = categorizeIP(dstIP)
345+
const deviceTags = deviceData?.tags || []
346+
const allTags = [...ipTags, ...deviceTags].filter((tag, index, arr) => arr.indexOf(tag) === index)
347+
317348
nodeMap.set(dstNodeId, {
318349
id: dstNodeId,
319350
ip: dstIP,
@@ -323,7 +354,8 @@ const NetworkView: React.FC = () => {
323354
txBytes: 0,
324355
rxBytes: 0,
325356
connections: 0,
326-
tags: categorizeIP(dstIP),
357+
tags: allTags,
358+
user: deviceData?.user,
327359
isTailscale,
328360
ips: [dstIP],
329361
incomingPorts: new Set<number>(),
@@ -337,11 +369,20 @@ const NetworkView: React.FC = () => {
337369
existingNode.ips = [...(existingNode.ips || []), dstIP]
338370
// Update tags to include IPv6 if this IP is IPv6
339371
const newTags = categorizeIP(dstIP)
340-
newTags.forEach(tag => {
372+
const deviceData = getDeviceData(dstIP, devices)
373+
const deviceTags = deviceData?.tags || []
374+
const combinedTags = [...newTags, ...deviceTags]
375+
376+
combinedTags.forEach(tag => {
341377
if (!existingNode.tags.includes(tag)) {
342378
existingNode.tags.push(tag)
343379
}
344380
})
381+
382+
// Update user if not already set
383+
if (!existingNode.user && deviceData?.user) {
384+
existingNode.user = deviceData.user
385+
}
345386
}
346387
}
347388

@@ -424,15 +465,56 @@ const NetworkView: React.FC = () => {
424465
// Apply filters
425466
const filteredData = useMemo(() => {
426467
let filteredNodes = nodes.filter(node => {
427-
// Search filter (search both IP and display name)
428-
if (searchQuery && !node.ip.toLowerCase().includes(searchQuery.toLowerCase()) &&
429-
!node.displayName.toLowerCase().includes(searchQuery.toLowerCase())) {
430-
return false
468+
// Enhanced search filter with tag:, user@, and ip: support
469+
if (searchQuery) {
470+
const query = searchQuery.toLowerCase().trim()
471+
472+
// Check for tag: search
473+
if (query.startsWith('tag:')) {
474+
const tagSearch = query.substring(4)
475+
const nodeTagsLower = node.tags.map(tag => tag.toLowerCase().replace('tag:', ''))
476+
if (!nodeTagsLower.some(tag => tag.includes(tagSearch))) {
477+
return false
478+
}
479+
}
480+
// Check for ip: search
481+
else if (query.startsWith('ip:')) {
482+
const ipSearch = query.substring(3)
483+
const allIPs = node.ips || [node.ip]
484+
if (!allIPs.some(ip => ip.toLowerCase().includes(ipSearch))) {
485+
return false
486+
}
487+
}
488+
// Check for user@ search
489+
else if (query.includes('@') && query.includes('user')) {
490+
const userSearch = query.replace('user@', '').replace('user:', '')
491+
if (!node.user || !node.user.toLowerCase().includes(userSearch)) {
492+
return false
493+
}
494+
}
495+
// Regular search (IP, display name, user, or tags)
496+
else {
497+
const allIPs = node.ips || [node.ip]
498+
const matchesIP = allIPs.some(ip => ip.toLowerCase().includes(query))
499+
const matchesName = node.displayName.toLowerCase().includes(query)
500+
const matchesUser = node.user?.toLowerCase().includes(query) || false
501+
const matchesTags = node.tags.some(tag =>
502+
tag.toLowerCase().replace('tag:', '').includes(query)
503+
)
504+
505+
if (!matchesIP && !matchesName && !matchesUser && !matchesTags) {
506+
return false
507+
}
508+
}
431509
}
432510

433-
// IP category filter
434-
if (ipCategoryFilters.size > 0 && !node.tags.some(tag => ipCategoryFilters.has(tag))) {
435-
return false
511+
// IP category filter (only for basic IP types, not device tags)
512+
if (ipCategoryFilters.size > 0) {
513+
const ipTypes = ['tailscale', 'private', 'public', 'derp']
514+
const nodeIpTypes = node.tags.filter(tag => ipTypes.includes(tag))
515+
if (!nodeIpTypes.some(tag => ipCategoryFilters.has(tag))) {
516+
return false
517+
}
436518
}
437519

438520
// IP version filter (IPv4/IPv6)
@@ -553,7 +635,12 @@ const NetworkView: React.FC = () => {
553635

554636
const dataProtocols = Array.from(new Set(links.map(l => l.protocol)))
555637
const dataTrafficTypes = Array.from(new Set(links.map(l => l.trafficType)))
556-
const dataIpCategories = Array.from(new Set(nodes.flatMap(n => n.tags).filter(tag => tag !== 'ipv6')))
638+
// Only include basic IP types, not device tags
639+
const dataIpCategories = Array.from(new Set(
640+
nodes.flatMap(n => n.tags).filter(tag =>
641+
['tailscale', 'private', 'public', 'derp'].includes(tag)
642+
)
643+
))
557644

558645
const uniqueProtocols = Array.from(new Set([...baseProtocols, ...dataProtocols]))
559646
const uniqueTrafficTypes = Array.from(new Set([...baseTrafficTypes, ...dataTrafficTypes]))
@@ -688,11 +775,17 @@ const NetworkView: React.FC = () => {
688775
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Search</label>
689776
<input
690777
type="text"
691-
placeholder="Search devices or IPs..."
778+
placeholder="Search devices, tag:k8s, ip:100.88, user@github..."
692779
value={searchQuery}
693780
onChange={(e) => setSearchQuery(e.target.value)}
694781
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400"
695782
/>
783+
<div className="mt-2 text-xs text-gray-500 dark:text-gray-400">
784+
<div><code className="bg-gray-100 dark:bg-gray-700 px-1 rounded">tag:k8s</code> - Find devices with specific tags</div>
785+
<div><code className="bg-gray-100 dark:bg-gray-700 px-1 rounded">ip:100.88</code> - Find devices by IP address</div>
786+
<div><code className="bg-gray-100 dark:bg-gray-700 px-1 rounded">user@github</code> - Find devices by user</div>
787+
<div>• Regular text searches device names, IPs, and tags</div>
788+
</div>
696789
</div>
697790

698791
{/* Time Range */}

0 commit comments

Comments
 (0)