Skip to content

Commit d01a1ec

Browse files
committed
Update dependencies and enhance error handling in backend and frontend
- Replaced `golang.org/x/oauth2` with `tailscale.com/client/tailscale/v2` in `go.mod` for improved Tailscale integration. - Updated `go.sum` to reflect changes in dependencies. - Modified CORS configuration in `main.go` to restrict origins in production. - Enhanced error logging in handlers for better debugging and tracking of issues. - Improved API response handling in `api.ts` to accommodate both array and object formats. - Refactored selection state management in `NetworkEdge` component for better performance and clarity. - Updated `NetworkView` to handle API responses more robustly, ensuring proper data validation and filtering.
1 parent 50914f1 commit d01a1ec

File tree

13 files changed

+184
-129
lines changed

13 files changed

+184
-129
lines changed

backend/go.mod

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/gin-contrib/gzip v1.2.3
1010
github.com/gin-gonic/gin v1.10.1
1111
github.com/joho/godotenv v1.4.0
12-
golang.org/x/oauth2 v0.30.0
12+
tailscale.com/client/tailscale/v2 v2.0.0-20250820140259-740bf1718a90
1313
)
1414

1515
require (
@@ -35,10 +35,9 @@ require (
3535
golang.org/x/arch v0.15.0 // indirect
3636
golang.org/x/crypto v0.36.0 // indirect
3737
golang.org/x/net v0.38.0 // indirect
38+
golang.org/x/oauth2 v0.30.0 // indirect
3839
golang.org/x/sys v0.31.0 // indirect
3940
golang.org/x/text v0.23.0 // indirect
40-
google.golang.org/appengine v1.6.8 // indirect
4141
google.golang.org/protobuf v1.36.6 // indirect
4242
gopkg.in/yaml.v3 v3.0.1 // indirect
43-
tailscale.com/client/tailscale/v2 v2.0.0-20250820140259-740bf1718a90 // indirect
4443
)

backend/go.sum

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF
3838
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
3939
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
4040
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
41-
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
42-
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
4341
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
42+
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
43+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
4444
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
4545
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
4646
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
@@ -99,52 +99,31 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
9999
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
100100
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
101101
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
102-
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
103102
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
104103
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
105-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
106104
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
107-
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
108105
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
109106
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
110-
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
111-
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
112107
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
113-
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
114108
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
115109
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
116110
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
117111
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
118-
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
119-
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
120-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
121112
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
122113
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
123114
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
124115
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
125-
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
126-
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
127116
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
128117
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
129118
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
130119
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
131-
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
132-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
133120
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
134121
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
135-
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
136-
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
137122
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
138123
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
139124
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
140-
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
141-
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
142-
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
143-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
144125
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
145-
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
146126
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
147-
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
148127
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
149128
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
150129
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=

backend/internal/handlers/handlers.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,14 @@ func (h *Handlers) GetNetworkLogs(c *gin.Context) {
9494
}
9595

9696
if et.Before(st) {
97-
c.JSON(400, gin.H{"error": "end time before start time"})
97+
log.Printf("ERROR GetNetworkLogs: end time before start time: %s < %s", end, start)
98+
c.JSON(http.StatusBadRequest, gin.H{"error": "end time before start time"})
9899
return
99100
}
100101

101102
now := time.Now()
102103
if st.After(now) {
104+
log.Printf("ERROR GetNetworkLogs: future start time not allowed: %s", start)
103105
c.JSON(http.StatusBadRequest, gin.H{"error": "future start time not allowed"})
104106
return
105107
}
@@ -180,6 +182,7 @@ func (h *Handlers) GetDeviceFlows(c *gin.Context) {
180182

181183
flows, err := h.tailscaleService.GetDeviceFlows(deviceID)
182184
if err != nil {
185+
log.Printf("ERROR GetDeviceFlows failed for device %s: %v", deviceID, err)
183186
c.JSON(http.StatusInternalServerError, gin.H{
184187
"error": "Failed to fetch device flows",
185188
"message": err.Error(),
@@ -193,6 +196,7 @@ func (h *Handlers) GetDeviceFlows(c *gin.Context) {
193196
func (h *Handlers) GetDNSNameservers(c *gin.Context) {
194197
nameservers, err := h.tailscaleService.GetDNSNameservers()
195198
if err != nil {
199+
log.Printf("ERROR GetDNSNameservers failed: %v", err)
196200
c.JSON(http.StatusInternalServerError, gin.H{
197201
"error": "Failed to fetch DNS nameservers",
198202
"message": err.Error(),

backend/internal/services/tailscale.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8+
"log"
89
"net/http"
910
"net/url"
1011
"sync"
@@ -140,7 +141,7 @@ func (ts *TailscaleService) makeRequestWithRetry(ctx context.Context, endpoint s
140141
}
141142

142143
if attempt < maxRetries {
143-
fmt.Printf("Request failed (attempt %d/%d), retrying in %v: %v\n", attempt+1, maxRetries+1, delay, err)
144+
log.Printf("Request failed (attempt %d/%d), retrying in %v: %v", attempt+1, maxRetries+1, delay, err)
144145
}
145146
}
146147

@@ -336,7 +337,7 @@ func (ts *TailscaleService) GetNetworkLogsChunked(start, end string, chunkSize t
336337
)
337338
if err != nil {
338339
// Log the error but continue with other chunks
339-
fmt.Printf("Error fetching logs for chunk %s to %s: %v\n",
340+
log.Printf("Error fetching logs for chunk %s to %s: %v",
340341
currentStart.Format(time.RFC3339),
341342
currentEnd.Format(time.RFC3339),
342343
err)
@@ -352,7 +353,9 @@ func (ts *TailscaleService) GetNetworkLogsChunked(start, end string, chunkSize t
352353

353354
// GetNetworkLogsChunkedParallel retrieves network logs in parallel chunks for large time ranges
354355
func (ts *TailscaleService) GetNetworkLogsChunkedParallel(start, end string, chunkSize time.Duration, maxConcurrency int) ([]interface{}, error) {
355-
return ts.GetNetworkLogsChunkedParallelWithContext(context.Background(), start, end, chunkSize, maxConcurrency)
356+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
357+
defer cancel()
358+
return ts.GetNetworkLogsChunkedParallelWithContext(ctx, start, end, chunkSize, maxConcurrency)
356359
}
357360

358361
// GetNetworkLogsChunkedParallelWithContext retrieves network logs in parallel chunks with context support
@@ -389,7 +392,7 @@ func (ts *TailscaleService) GetNetworkLogsChunkedParallelWithContext(ctx context
389392
return []interface{}{result}, nil
390393
}
391394

392-
// Channel for collecting results
395+
// Channel for collecting results - buffered to prevent goroutine leaks
393396
type result struct {
394397
index int
395398
logs interface{}
@@ -458,17 +461,23 @@ func (ts *TailscaleService) GetNetworkLogsChunkedParallelWithContext(ctx context
458461

459462
// Close results channel when all goroutines complete
460463
go func() {
464+
defer close(resultsChan)
461465
wg.Wait()
462-
close(resultsChan)
463466
}()
464467

465468
// Collect results
466469
results := make([]interface{}, len(chunks))
467470
var hasError bool
468471

469472
for res := range resultsChan {
473+
// Bounds check to prevent slice access panic
474+
if res.index < 0 || res.index >= len(results) {
475+
log.Printf("Warning: invalid result index %d, skipping", res.index)
476+
continue
477+
}
478+
470479
if res.err != nil {
471-
fmt.Printf("Error fetching chunk %d: %v\n", res.index, res.err)
480+
log.Printf("Error fetching chunk %d: %v", res.index, res.err)
472481
hasError = true
473482
// Store nil for failed chunks
474483
results[res.index] = nil

backend/internal/utils/http.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import (
88
)
99

1010
func IsRetryable(err error) bool {
11-
if err == nil || err == context.DeadlineExceeded {
12-
return true
11+
if err == nil {
12+
return false
13+
}
14+
15+
if err == context.DeadlineExceeded {
16+
return false
1317
}
1418

1519
errStr := err.Error()

backend/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ func main() {
6868

6969
corsConfig := cors.DefaultConfig()
7070
if cfg.Environment == "production" {
71-
corsConfig.AllowAllOrigins = true
71+
corsConfig.AllowOrigins = []string{"https://tsflow.production.com"}
72+
corsConfig.AllowAllOrigins = false
7273
} else {
7374
corsConfig.AllowOrigins = []string{"http://localhost:3000", "http://localhost:5173"}
7475
}

frontend/package-lock.json

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/components/NetworkEdge.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,23 @@ const NetworkEdge = memo<EdgeProps>(({
5656
targetPosition,
5757
});
5858

59-
// Get highlighting state from context
59+
// Get highlighting state from context with memoization
6060
const linkData = data as NetworkLinkData;
6161
const { highlightedEdges, selectedNode, selectedLink } = useSelection();
62-
const isHighlighted = highlightedEdges.has(id) || selected;
63-
const isSelectedLink = selectedLink &&
64-
(typeof selectedLink.source === 'string' ? selectedLink.source : selectedLink.source.id) === linkData.source &&
65-
(typeof selectedLink.target === 'string' ? selectedLink.target : selectedLink.target.id) === linkData.target;
66-
const isSelected = isSelectedLink || selected;
67-
const hasSelection = selectedNode !== null || selectedLink !== null;
68-
const isDimmed = hasSelection && !isHighlighted;
62+
63+
const selectionState = useMemo(() => {
64+
const isHighlighted = highlightedEdges.has(id) || selected;
65+
const isSelectedLink = selectedLink &&
66+
(typeof selectedLink.source === 'string' ? selectedLink.source : selectedLink.source.id) === linkData?.source &&
67+
(typeof selectedLink.target === 'string' ? selectedLink.target : selectedLink.target.id) === linkData?.target;
68+
const isSelected = isSelectedLink || selected;
69+
const hasSelection = selectedNode !== null || selectedLink !== null;
70+
const isDimmed = hasSelection && !isHighlighted;
71+
72+
return { isHighlighted, isSelected, isDimmed, hasSelection };
73+
}, [highlightedEdges, selectedNode, selectedLink, selected, id, linkData]);
74+
75+
const { isHighlighted, isSelected, isDimmed } = selectionState;
6976

7077
// Memoize edge styling calculations
7178
const edgeStyle = useMemo(() => {
@@ -154,8 +161,8 @@ const NetworkEdge = memo<EdgeProps>(({
154161
}}
155162
/>
156163

157-
{/* Enhanced edge label with traffic information */}
158-
{data && selected && formattedData && (
164+
{/* Enhanced edge label with traffic information - only show when selected or highlighted */}
165+
{data && (selected || isHighlighted) && formattedData && (
159166
<EdgeLabelRenderer>
160167
<div
161168
style={{

frontend/src/components/NetworkGraph.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from 'react'
2-
// @ts-ignore - Temporarily ignore React Flow type issues while we fix them
32
import ReactFlowGraph from './ReactFlowGraph'
43

54
// Re-export the interfaces to maintain compatibility
@@ -48,7 +47,6 @@ interface NetworkGraphProps {
4847
}
4948

5049
const NetworkGraph: React.FC<NetworkGraphProps> = (props) => {
51-
// @ts-ignore - Temporarily ignore React Flow type issues
5250
return <ReactFlowGraph {...props} />
5351
}
5452

frontend/src/components/NetworkNode.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,18 +175,18 @@ const NetworkNode = memo<NodeProps>(({ data, selected }) => {
175175
zIndex: isHighlighted ? 10 : 1,
176176
}}
177177
>
178-
{/* Invisible handles for connections */}
178+
{/* Invisible handles for connections - centered properly */}
179179
<Handle
180180
type="source"
181181
position={Position.Top}
182-
className="!opacity-0 !pointer-events-none !w-0 !h-0"
183-
style={{ left: '50%', top: '50%', transform: 'translate(-50%, -50%)' }}
182+
className="!opacity-0 !pointer-events-none"
183+
style={{ left: '50%', top: '50%', transform: 'translate(-50%, -50%)', width: 1, height: 1 }}
184184
/>
185185
<Handle
186186
type="target"
187-
position={Position.Top}
188-
className="!opacity-0 !pointer-events-none !w-0 !h-0"
189-
style={{ left: '50%', top: '50%', transform: 'translate(-50%, -50%)' }}
187+
position={Position.Bottom}
188+
className="!opacity-0 !pointer-events-none"
189+
style={{ left: '50%', bottom: '50%', transform: 'translate(-50%, 50%)', width: 1, height: 1 }}
190190
/>
191191

192192
{/* Header Section */}

0 commit comments

Comments
 (0)