Skip to content

Commit ed4588b

Browse files
committed
Enhance CORS configuration for production, improve logging, and implement pagination in Logs and NetworkView components. Update .gitignore and fix minor formatting issues in Vite config and API files.
1 parent c18fdb5 commit ed4588b

File tree

7 files changed

+256
-160
lines changed

7 files changed

+256
-160
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Thumbs.db
7070
coverage/
7171
.nyc_output
7272
.cache/
73-
.parcel-cache/
73+
.parcel-cache/
7474

7575
# Docker
7676
.dockerignore

backend/main.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ func main() {
3939

4040
// CORS middleware
4141
corsConfig := cors.DefaultConfig()
42-
corsConfig.AllowOrigins = []string{"http://localhost:3000", "http://localhost:5173"} // Vite dev server ports
42+
if cfg.Environment == "production" {
43+
corsConfig.AllowAllOrigins = true // Allow all origins in production since backend serves frontend
44+
} else {
45+
corsConfig.AllowOrigins = []string{"http://localhost:3000", "http://localhost:5173"} // Vite dev server ports
46+
}
4347
corsConfig.AllowCredentials = true
4448
corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
4549
corsConfig.AllowHeaders = []string{"Origin", "Content-Type", "Accept", "Authorization"}
@@ -65,13 +69,16 @@ func main() {
6569
distPath = "../frontend/dist" // Development path
6670
}
6771

72+
log.Printf("Serving static files from: %s", distPath)
73+
6874
// Serve all static assets (CSS, JS, images, etc.)
6975
router.Static("/assets", distPath+"/assets")
7076
router.StaticFile("/favicon.svg", distPath+"/favicon.svg")
7177

7278
// Serve index.html for root and all unmatched routes (client-side routing)
7379
router.StaticFile("/", distPath+"/index.html")
7480
router.NoRoute(func(c *gin.Context) {
81+
log.Printf("Serving SPA route for: %s", c.Request.URL.Path)
7582
c.File(distPath + "/index.html")
7683
})
7784

@@ -85,7 +92,7 @@ func main() {
8592
log.Printf("Tailnet: %s", cfg.TailscaleTailnet)
8693
log.Printf("Environment: %s", cfg.Environment)
8794

88-
if err := router.Run(":" + port); err != nil {
95+
if err := router.Run("0.0.0.0:" + port); err != nil {
8996
log.Fatalf("Failed to start server: %v", err)
9097
}
9198
}

frontend/src/components/Layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export default function Layout({ children, networkStats, onResetZoom, onClearSel
161161
onClick={onClearSelection}
162162
className="px-3 py-2 text-sm bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-500"
163163
>
164-
Clear Selection
164+
Reset All Filters
165165
</button>
166166
</>
167167
)}

frontend/src/lib/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export const fetcher = async (url: string) => {
150150
const deviceId = url.split('/')[2] // Extract device ID from URL like /devices/123/flows
151151
return await tailscaleAPI.getDeviceFlows(deviceId)
152152
}
153-
153+
154154
throw new Error(`Unknown API endpoint: ${url}`)
155155
} catch (error: unknown) {
156156
// Re-throw with better context

frontend/src/pages/Logs.tsx

Lines changed: 147 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect, useMemo } from 'react'
2-
import { Search, Download, Calendar, RefreshCw } from 'lucide-react'
2+
import { Search, Download, Calendar, RefreshCw, ChevronLeft, ChevronRight } from 'lucide-react'
33
import useSWR from 'swr'
44
import Layout from '@/components/Layout'
55
import { fetcher } from '@/lib/api'
@@ -51,6 +51,10 @@ export default function Logs() {
5151
const [trafficTypeFilter, setTrafficTypeFilter] = useState<string>('all')
5252
const [selectedEntry, setSelectedEntry] = useState<LogEntry | null>(null)
5353

54+
// Pagination states
55+
const [currentPage, setCurrentPage] = useState(1)
56+
const [itemsPerPage, setItemsPerPage] = useState(50)
57+
5458
// Custom time range states
5559
const [useCustomTimeRange, setUseCustomTimeRange] = useState(false)
5660
const [startDate, setStartDate] = useState('')
@@ -78,7 +82,7 @@ export default function Logs() {
7882
const { data: networkLogsData, mutate: refetchNetworkLogs } = useSWR(networkLogsApiUrl, fetcher, {
7983
errorRetryCount: 2,
8084
revalidateOnFocus: false,
81-
refreshInterval: 30000
85+
refreshInterval: 120000
8286
})
8387

8488
const networkLogs = (Array.isArray(networkLogsData) && networkLogsData.length > 0 && 'logged' in networkLogsData[0]) ? networkLogsData : []
@@ -162,6 +166,17 @@ export default function Logs() {
162166
})
163167
}, [flattenedEntries, searchQuery, protocolFilter, trafficTypeFilter])
164168

169+
// Reset to first page when filters change
170+
useEffect(() => {
171+
setCurrentPage(1)
172+
}, [searchQuery, protocolFilter, trafficTypeFilter, useCustomTimeRange, startDate, endDate])
173+
174+
// Calculate pagination values
175+
const totalPages = Math.ceil(filteredEntries.length / itemsPerPage)
176+
const startIndex = (currentPage - 1) * itemsPerPage
177+
const endIndex = startIndex + itemsPerPage
178+
const currentEntries = filteredEntries.slice(startIndex, endIndex)
179+
165180
// Get unique protocols and traffic types for filters
166181
const uniqueProtocols = Array.from(new Set(flattenedEntries.map(e => getProtocolName(e.traffic.proto))))
167182
const uniqueTrafficTypes = Array.from(new Set(flattenedEntries.map(e => e.traffic.type)))
@@ -193,24 +208,19 @@ export default function Logs() {
193208
return (
194209
<Layout>
195210
<div className="flex flex-col h-full overflow-hidden">
196-
{/* Unified Header */}
211+
{/* Stats Header */}
197212
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-3 flex-shrink-0">
198213
<div className="flex items-center justify-between">
199-
<div className="flex items-center space-x-4">
200-
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Logs</h2>
201-
<div className="flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400">
202-
<span>{filteredEntries.length.toLocaleString()} of {flattenedEntries.length.toLocaleString()} flows</span>
203-
<span></span>
204-
<span>{networkLogs.length.toLocaleString()} entries</span>
205-
<span></span>
206-
<span>{uniqueProtocols.length} protocols</span>
207-
{useCustomTimeRange && startDate && endDate && (
208-
<>
209-
<span></span>
210-
<span>{new Date(startDate).toLocaleDateString()} - {new Date(endDate).toLocaleDateString()}</span>
211-
</>
212-
)}
213-
</div>
214+
<div className="flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400">
215+
<span>{filteredEntries.length.toLocaleString()} flows from {networkLogs.length.toLocaleString()} log entries</span>
216+
<span></span>
217+
<span>{uniqueProtocols.length} protocols</span>
218+
<span></span>
219+
<span>Page {currentPage} of {totalPages}</span>
220+
<span></span>
221+
<span>{useCustomTimeRange && startDate && endDate ?
222+
`${new Date(startDate).toLocaleDateString()} - ${new Date(endDate).toLocaleDateString()}` :
223+
'Last 5 minutes'}</span>
214224
</div>
215225
<div className="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400">
216226
<Calendar className="w-4 h-4" />
@@ -324,6 +334,25 @@ export default function Logs() {
324334
</select>
325335
</div>
326336

337+
{/* Items per page */}
338+
<div className="mb-6">
339+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Items per page</label>
340+
<select
341+
value={itemsPerPage}
342+
onChange={(e) => {
343+
setItemsPerPage(Number(e.target.value))
344+
setCurrentPage(1) // Reset to first page
345+
}}
346+
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
347+
>
348+
<option value={25}>25</option>
349+
<option value={50}>50</option>
350+
<option value={100}>100</option>
351+
<option value={200}>200</option>
352+
<option value={500}>500</option>
353+
</select>
354+
</div>
355+
327356
{/* Export */}
328357
<button
329358
onClick={exportLogs}
@@ -337,19 +366,73 @@ export default function Logs() {
337366
<div className="mt-6 bg-gray-50 dark:bg-gray-700 p-3 rounded-lg border border-gray-200 dark:border-gray-600">
338367
<h4 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2">Summary</h4>
339368
<div className="space-y-1 text-xs text-gray-600 dark:text-gray-300">
340-
<div>Total Entries: {flattenedEntries.length.toLocaleString()}</div>
369+
<div>Total Flows: {flattenedEntries.length.toLocaleString()}</div>
341370
<div>Filtered: {filteredEntries.length.toLocaleString()}</div>
342-
<div>Network Log Entries: {networkLogs.length.toLocaleString()}</div>
343-
{useCustomTimeRange && startDate && endDate && (
344-
<div>Time Range: {new Date(startDate).toLocaleDateString()} - {new Date(endDate).toLocaleDateString()}</div>
345-
)}
371+
<div>Showing: {Math.min(itemsPerPage, filteredEntries.length - startIndex)} of {filteredEntries.length.toLocaleString()}</div>
372+
<div>Log Entries: {networkLogs.length.toLocaleString()}</div>
373+
<div>Time Range: {useCustomTimeRange && startDate && endDate ?
374+
`${new Date(startDate).toLocaleDateString()} - ${new Date(endDate).toLocaleDateString()}` :
375+
'Last 5 minutes'}</div>
346376
</div>
347377
</div>
348378
</div>
349379
</div>
350380

351381
{/* Main Content */}
352382
<div className="flex-1 flex flex-col min-w-0">
383+
{/* Pagination Controls - Top */}
384+
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-3 flex-shrink-0">
385+
<div className="flex items-center justify-between">
386+
<div className="text-sm text-gray-600 dark:text-gray-400">
387+
Showing {startIndex + 1} to {Math.min(endIndex, filteredEntries.length)} of {filteredEntries.length.toLocaleString()} flows
388+
</div>
389+
<div className="flex items-center space-x-2">
390+
<button
391+
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
392+
disabled={currentPage === 1}
393+
className="p-2 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
394+
>
395+
<ChevronLeft className="w-4 h-4" />
396+
</button>
397+
<div className="flex items-center space-x-1">
398+
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
399+
let pageNum
400+
if (totalPages <= 5) {
401+
pageNum = i + 1
402+
} else if (currentPage <= 3) {
403+
pageNum = i + 1
404+
} else if (currentPage >= totalPages - 2) {
405+
pageNum = totalPages - 4 + i
406+
} else {
407+
pageNum = currentPage - 2 + i
408+
}
409+
410+
return (
411+
<button
412+
key={pageNum}
413+
onClick={() => setCurrentPage(pageNum)}
414+
className={`px-3 py-1 rounded-md text-sm transition-colors ${
415+
currentPage === pageNum
416+
? 'bg-blue-600 text-white'
417+
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
418+
}`}
419+
>
420+
{pageNum}
421+
</button>
422+
)
423+
})}
424+
</div>
425+
<button
426+
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
427+
disabled={currentPage === totalPages}
428+
className="p-2 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
429+
>
430+
<ChevronRight className="w-4 h-4" />
431+
</button>
432+
</div>
433+
</div>
434+
</div>
435+
353436
{/* Logs Table */}
354437
<div className="flex-1 overflow-auto">
355438
<table className="w-full">
@@ -366,7 +449,7 @@ export default function Logs() {
366449
</tr>
367450
</thead>
368451
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
369-
{filteredEntries.length === 0 ? (
452+
{currentEntries.length === 0 ? (
370453
<tr>
371454
<td colSpan={8} className="px-6 py-12 text-center">
372455
<div className="text-gray-500 dark:text-gray-400">
@@ -380,7 +463,7 @@ export default function Logs() {
380463
</td>
381464
</tr>
382465
) : (
383-
filteredEntries.map((entry) => (
466+
currentEntries.map((entry) => (
384467
<tr
385468
key={entry.index}
386469
className="hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors"
@@ -425,6 +508,45 @@ export default function Logs() {
425508
</tbody>
426509
</table>
427510
</div>
511+
512+
{/* Pagination Controls - Bottom */}
513+
<div className="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 px-6 py-3 flex-shrink-0">
514+
<div className="flex items-center justify-center">
515+
<div className="flex items-center space-x-2">
516+
<button
517+
onClick={() => setCurrentPage(1)}
518+
disabled={currentPage === 1}
519+
className="px-3 py-1 rounded-md text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
520+
>
521+
First
522+
</button>
523+
<button
524+
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
525+
disabled={currentPage === 1}
526+
className="p-2 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
527+
>
528+
<ChevronLeft className="w-4 h-4" />
529+
</button>
530+
<span className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">
531+
Page {currentPage} of {totalPages}
532+
</span>
533+
<button
534+
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
535+
disabled={currentPage === totalPages}
536+
className="p-2 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
537+
>
538+
<ChevronRight className="w-4 h-4" />
539+
</button>
540+
<button
541+
onClick={() => setCurrentPage(totalPages)}
542+
disabled={currentPage === totalPages}
543+
className="px-3 py-1 rounded-md text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
544+
>
545+
Last
546+
</button>
547+
</div>
548+
</div>
549+
</div>
428550
</div>
429551

430552
{/* Details Panel */}

0 commit comments

Comments
 (0)