From 3d1135aa69b6e7fe05d72d9a5dff79e095b84f59 Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Fri, 16 Jan 2026 12:44:37 +0530 Subject: [PATCH 1/3] feat: add network health dashboard --- .gitignore | 1 + Makefile | 47 ++--- tools/network-dashboard/go.mod | 3 + tools/network-dashboard/main.go | 299 ++++++++++++++++++++++++++++++++ 4 files changed, 330 insertions(+), 20 deletions(-) create mode 100644 tools/network-dashboard/go.mod create mode 100644 tools/network-dashboard/main.go diff --git a/.gitignore b/.gitignore index 2228954..368d53a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ identity grpc_p2p_client/p2p-client grpc_p2p_client/p2p-multi-publish grpc_p2p_client/p2p-multi-subscribe +tools/network-dashboard/network-dashboard # Test files grpc_p2p_client/test-ips.txt diff --git a/Makefile b/Makefile index e473647..d37f832 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,13 @@ IDENTITY_DIR := ./identity P2P_CLIENT := $(P2P_CLIENT_DIR)/p2p-client PROXY_CLIENT := $(PROXY_CLIENT_DIR)/proxy-client KEYGEN_BINARY := keygen/generate-p2p-key +DASHBOARD_BINARY := tools/network-dashboard/network-dashboard # Scripts SCRIPTS := ./script/generate-identity.sh ./script/proxy_client.sh ./test_suite.sh # Helper targets (not shown in help) -.PHONY: $(P2P_CLIENT) $(PROXY_CLIENT) $(KEYGEN_BINARY) setup-scripts +.PHONY: $(P2P_CLIENT) $(PROXY_CLIENT) $(KEYGEN_BINARY) $(DASHBOARD_BINARY) setup-scripts $(P2P_CLIENT): @cd $(P2P_CLIENT_DIR) && go build -o p2p-client ./cmd/single/ @@ -24,6 +25,9 @@ $(PROXY_CLIENT): $(KEYGEN_BINARY): @cd keygen && go build -o generate-p2p-key ./generate_p2p_key.go +$(DASHBOARD_BINARY): + @cd tools/network-dashboard && go build -o network-dashboard . + setup-scripts: @chmod +x $(SCRIPTS) @@ -41,8 +45,7 @@ help: ## Show help @echo " # Publish multiple messages with options" @echo " $(P2P_CLIENT) -mode=publish -topic=\"testtopic\" -msg=\"Random Message\" --addr=\"127.0.0.1:33221\" -count=10 -sleep=1s" -build: $(P2P_CLIENT) $(PROXY_CLIENT) ## Build all client binaries - @echo "All clients built successfully" +build: $(P2P_CLIENT) $(PROXY_CLIENT) $(DASHBOARD_BINARY) ## Build all client binaries generate-identity: ## Generate P2P identity (if missing) @mkdir -p $(IDENTITY_DIR) @@ -86,60 +89,64 @@ publish: $(P2P_CLIENT) generate-identity ## publish message to p2p topic: make p extra_args="$$extra_args -sleep=$$sleep_val"; \ fi; \ if [ "$$message" = "random" ]; then \ - echo "Publishing random messages to topic=$$topic addr=$$addr count=$${count:-1} sleep=$${sleep:-default}"; \ $(P2P_CLIENT) -mode=publish -topic="$$topic" -msg="random" --addr="$$addr" $$extra_args; \ else \ - echo "Publishing message '$$message' to topic=$$topic addr=$$addr"; \ $(P2P_CLIENT) -mode=publish -topic="$$topic" -msg="$$message" --addr="$$addr" $$extra_args; \ fi test: $(P2P_CLIENT) $(PROXY_CLIENT) $(KEYGEN_BINARY) ## Run tests for Go clients - @echo "All Go clients built successfully" lint: ## Run golangci-lint - @echo "Running golangci-lint..." @cd $(P2P_CLIENT_DIR) && golangci-lint run --skip-dirs-use-default || echo "Linting issues found in P2P client" @cd $(PROXY_CLIENT_DIR) && golangci-lint run --skip-dirs-use-default || echo "Linting issues found in Proxy client" @cd keygen && golangci-lint run --skip-dirs-use-default || echo "Linting issues found in Keygen" - @echo "Linting completed" test-docker: setup-scripts ## Test Docker Compose setup - @echo "Testing Docker Compose setup..." @./script/generate-identity.sh @docker-compose -f docker-compose-optimum.yml up --build -d # Alternative: Use GossipSub protocol instead # @docker-compose -f docker-compose-gossipsub.yml up --build -d - @echo "Waiting for services to be ready..." @sleep 30 @docker-compose -f docker-compose-optimum.yml ps - @echo "Running test suite..." @./test_suite.sh - @echo "Docker setup test completed" test-scripts: setup-scripts ## Test shell scripts - @echo "Script tests completed" validate: ## Validate configuration files - @echo "Validating configuration files..." @docker-compose -f docker-compose-optimum.yml config # Alternative: Validate GossipSub configuration instead # @docker-compose -f docker-compose-gossipsub.yml config @cd $(P2P_CLIENT_DIR) && go mod verify @cd $(PROXY_CLIENT_DIR) && go mod verify @cd keygen && go mod verify - @echo "Configuration validation completed" ci: test lint test-docker test-scripts validate ## Run all CI checks locally - @echo "All CI checks passed!" + +dashboard: $(DASHBOARD_BINARY) ## Show network health dashboard: make dashboard [local|remote] [proxy-base=URL] [node-base=URL] + @set -e; \ + mode="$(word 2,$(MAKECMDGOALS))"; \ + proxy_base="$(proxy-base)"; \ + node_base="$(node-base)"; \ + if [ -z "$$mode" ] || [ "$$mode" = "local" ]; then \ + $(DASHBOARD_BINARY) -local; \ + elif [ "$$mode" = "remote" ]; then \ + args=""; \ + if [ -n "$$proxy_base" ]; then args="$$args -proxy-base=$$proxy_base"; fi; \ + if [ -n "$$node_base" ]; then args="$$args -node-base=$$node_base"; fi; \ + if [ -z "$$args" ]; then \ + exit 1; \ + fi; \ + $(DASHBOARD_BINARY) $$args; \ + else \ + exit 1; \ + fi clean: ## Clean build artifacts - @echo "Cleaning build artifacts..." - @rm -f $(P2P_CLIENT) $(PROXY_CLIENT) $(KEYGEN_BINARY) - @echo "Clean complete!" + @rm -f $(P2P_CLIENT) $(PROXY_CLIENT) $(KEYGEN_BINARY) $(DASHBOARD_BINARY) # Prevent make from interpreting arguments as targets %: @: .DEFAULT_GOAL := help -.PHONY: help build generate-identity subscribe publish test lint test-docker test-scripts validate ci clean setup-scripts +.PHONY: help build generate-identity subscribe publish test lint test-docker test-scripts validate ci clean setup-scripts dashboard diff --git a/tools/network-dashboard/go.mod b/tools/network-dashboard/go.mod new file mode 100644 index 0000000..5454782 --- /dev/null +++ b/tools/network-dashboard/go.mod @@ -0,0 +1,3 @@ +module network-dashboard + +go 1.21 diff --git a/tools/network-dashboard/main.go b/tools/network-dashboard/main.go new file mode 100644 index 0000000..7278715 --- /dev/null +++ b/tools/network-dashboard/main.go @@ -0,0 +1,299 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" +) + +type NodeHealth struct { + Status string `json:"status"` + Mode string `json:"mode,omitempty"` + CPUUsed string `json:"cpu_used"` + MemoryUsed string `json:"memory_used"` + DiskUsed string `json:"disk_used"` + Country string `json:"country"` + CountryISO string `json:"country_iso"` +} + +type NodeState struct { + PubKey string `json:"pub_key"` + Peers []string `json:"peers"` + Addresses []string `json:"addresses"` + Topics []string `json:"topics"` + Country string `json:"country"` + CountryISO string `json:"country_iso"` +} + +type ProxyHealth struct { + Status string `json:"status"` + CPUUsed string `json:"cpu_used"` + MemoryUsed string `json:"memory_used"` + DiskUsed string `json:"disk_used"` + Country string `json:"country"` + CountryISO string `json:"country_iso"` +} + +type NodeCountries struct { + Countries map[string]string `json:"countries"` + CountryISOs map[string]string `json:"country_isos"` + Count int `json:"count"` +} + +type NodeInfo struct { + Name string + URL string + Health *NodeHealth + State *NodeState + Available bool + Error string +} + +type ProxyInfo struct { + Name string + URL string + Health *ProxyHealth + Available bool + Error string +} + +func fetchJSON(url string, target interface{}) error { + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("HTTP %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + return json.Unmarshal(body, target) +} + +func fetchNodeInfo(name, baseURL string) NodeInfo { + info := NodeInfo{Name: name, URL: baseURL} + + health := &NodeHealth{} + if err := fetchJSON(baseURL+"/api/v1/health", health); err != nil { + info.Error = err.Error() + return info + } + info.Health = health + + state := &NodeState{} + if err := fetchJSON(baseURL+"/api/v1/node-state", state); err != nil { + info.Error = err.Error() + return info + } + info.State = state + info.Available = true + + return info +} + +func fetchProxyInfo(name, baseURL string) ProxyInfo { + info := ProxyInfo{Name: name, URL: baseURL} + + health := &ProxyHealth{} + if err := fetchJSON(baseURL+"/api/v1/health", health); err != nil { + info.Error = err.Error() + return info + } + info.Health = health + info.Available = true + + return info +} + +func printDashboard(nodes []NodeInfo, proxies []ProxyInfo, nodeCountries *NodeCountries) { + fmt.Println(strings.Repeat("=", 100)) + fmt.Printf("%-50s %s\n", "mump2p NETWORK DASHBOARD", time.Now().Format("2006-01-02 15:04:05")) + fmt.Println(strings.Repeat("=", 100)) + fmt.Println() + + if len(proxies) > 0 { + fmt.Println("PROXIES") + fmt.Println(strings.Repeat("-", 100)) + fmt.Printf("%-15s %-8s %-10s %-10s %-10s %-15s %-20s\n", + "Name", "Status", "CPU %", "Memory %", "Disk %", "Country", "URL") + fmt.Println(strings.Repeat("-", 100)) + for _, p := range proxies { + status := "DOWN" + cpu, mem, disk, country := "N/A", "N/A", "N/A", "N/A" + if p.Available && p.Health != nil { + status = p.Health.Status + cpu = p.Health.CPUUsed + mem = p.Health.MemoryUsed + disk = p.Health.DiskUsed + country = p.Health.Country + } + fmt.Printf("%-15s %-8s %-10s %-10s %-10s %-15s %-20s\n", + p.Name, status, cpu, mem, disk, country, p.URL) + } + fmt.Println() + } + + if len(nodes) > 0 { + fmt.Println("P2P NODES") + fmt.Println(strings.Repeat("-", 100)) + fmt.Printf("%-15s %-8s %-10s %-10s %-10s %-8s %-8s %-15s %-20s\n", + "Name", "Status", "CPU %", "Memory %", "Disk %", "Peers", "Topics", "Country", "URL") + fmt.Println(strings.Repeat("-", 100)) + for _, n := range nodes { + status := "DOWN" + cpu, mem, disk, country := "N/A", "N/A", "N/A", "N/A" + peers, topics := "0", "0" + if n.Available { + if n.Health != nil { + status = n.Health.Status + cpu = n.Health.CPUUsed + mem = n.Health.MemoryUsed + disk = n.Health.DiskUsed + country = n.Health.Country + } + if n.State != nil { + peers = fmt.Sprintf("%d", len(n.State.Peers)) + topics = fmt.Sprintf("%d", len(n.State.Topics)) + } + } + fmt.Printf("%-15s %-8s %-10s %-10s %-10s %-8s %-8s %-15s %-20s\n", + n.Name, status, cpu, mem, disk, peers, topics, country, n.URL) + } + fmt.Println() + + fmt.Println("NODE DETAILS") + fmt.Println(strings.Repeat("-", 100)) + for _, n := range nodes { + if !n.Available { + fmt.Printf("%s: %s\n", n.Name, n.Error) + continue + } + if n.State == nil { + continue + } + fmt.Printf("%s (Peer ID: %s)\n", n.Name, n.State.PubKey) + fmt.Printf(" Peers: %d\n", len(n.State.Peers)) + if len(n.State.Peers) > 0 { + fmt.Printf(" Peer IDs: %s\n", strings.Join(n.State.Peers[:min(5, len(n.State.Peers))], ", ")) + if len(n.State.Peers) > 5 { + fmt.Printf(" ... and %d more\n", len(n.State.Peers)-5) + } + } + fmt.Printf(" Topics: %d", len(n.State.Topics)) + if len(n.State.Topics) > 0 { + fmt.Printf(" [%s]\n", strings.Join(n.State.Topics, ", ")) + } else { + fmt.Println() + } + fmt.Printf(" Addresses: %s\n", strings.Join(n.State.Addresses, ", ")) + fmt.Println() + } + } + + if nodeCountries != nil && nodeCountries.Count > 0 { + fmt.Println("NODE COUNTRIES") + fmt.Println(strings.Repeat("-", 100)) + countryCount := make(map[string]int) + for _, country := range nodeCountries.Countries { + countryCount[country]++ + } + for country, count := range countryCount { + fmt.Printf("%s: %d node(s)\n", country, count) + } + fmt.Println() + } + + fmt.Println(strings.Repeat("=", 100)) +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func main() { + var ( + proxyURLs = flag.String("proxies", "", "Comma-separated list of proxy URLs (e.g., http://localhost:8081,http://localhost:8082)") + nodeURLs = flag.String("nodes", "", "Comma-separated list of node URLs (e.g., http://localhost:9091,http://localhost:9092)") + proxyBase = flag.String("proxy-base", "", "Base URL for proxies (e.g., http://localhost) - will append :8081,:8082") + nodeBase = flag.String("node-base", "", "Base URL for nodes (e.g., http://localhost) - will append :9091,:9092,:9093,:9094") + local = flag.Bool("local", false, "Use localhost defaults (proxies: 8081,8082; nodes: 9091-9094)") + ) + flag.Parse() + + var proxies []ProxyInfo + var nodes []NodeInfo + + if *local { + proxyURLs := []string{"http://localhost:8081", "http://localhost:8082"} + for i, url := range proxyURLs { + proxies = append(proxies, fetchProxyInfo(fmt.Sprintf("proxy-%d", i+1), url)) + } + nodeURLs := []string{"http://localhost:9091", "http://localhost:9092", "http://localhost:9093", "http://localhost:9094"} + for i, url := range nodeURLs { + nodes = append(nodes, fetchNodeInfo(fmt.Sprintf("p2pnode-%d", i+1), url)) + } + } else if *proxyBase != "" { + ports := []string{":8080"} + for i, port := range ports { + url := *proxyBase + port + proxies = append(proxies, fetchProxyInfo(fmt.Sprintf("proxy-%d", i+1), url)) + } + } else if *proxyURLs != "" { + urls := strings.Split(*proxyURLs, ",") + for i, url := range urls { + url = strings.TrimSpace(url) + if url == "" { + continue + } + proxies = append(proxies, fetchProxyInfo(fmt.Sprintf("proxy-%d", i+1), url)) + } + } + + if *nodeBase != "" { + ports := []string{":8081"} + for i, port := range ports { + url := *nodeBase + port + nodes = append(nodes, fetchNodeInfo(fmt.Sprintf("p2pnode-%d", i+1), url)) + } + } else if *nodeURLs != "" { + urls := strings.Split(*nodeURLs, ",") + for i, url := range urls { + url = strings.TrimSpace(url) + if url == "" { + continue + } + nodes = append(nodes, fetchNodeInfo(fmt.Sprintf("p2pnode-%d", i+1), url)) + } + } + + if len(proxies) == 0 && len(nodes) == 0 { + fmt.Fprintf(os.Stderr, "Error: No proxies or nodes specified. Use -local, -proxy-base/-node-base, or -proxies/-nodes flags.\n") + flag.Usage() + os.Exit(1) + } + + var nodeCountries *NodeCountries + if len(proxies) > 0 && proxies[0].Available { + nc := &NodeCountries{} + if err := fetchJSON(proxies[0].URL+"/api/v1/node-countries", nc); err == nil { + nodeCountries = nc + } + } + + printDashboard(nodes, proxies, nodeCountries) +} From 583cb9149c5d589ebc0089e66ac8f0517d66ec7a Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Fri, 16 Jan 2026 12:49:24 +0530 Subject: [PATCH 2/3] fix --- tools/network-dashboard/main.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tools/network-dashboard/main.go b/tools/network-dashboard/main.go index 7278715..13c99dc 100644 --- a/tools/network-dashboard/main.go +++ b/tools/network-dashboard/main.go @@ -13,21 +13,17 @@ import ( type NodeHealth struct { Status string `json:"status"` - Mode string `json:"mode,omitempty"` CPUUsed string `json:"cpu_used"` MemoryUsed string `json:"memory_used"` DiskUsed string `json:"disk_used"` Country string `json:"country"` - CountryISO string `json:"country_iso"` } type NodeState struct { - PubKey string `json:"pub_key"` - Peers []string `json:"peers"` - Addresses []string `json:"addresses"` - Topics []string `json:"topics"` - Country string `json:"country"` - CountryISO string `json:"country_iso"` + PubKey string `json:"pub_key"` + Peers []string `json:"peers"` + Addresses []string `json:"addresses"` + Topics []string `json:"topics"` } type ProxyHealth struct { @@ -36,13 +32,11 @@ type ProxyHealth struct { MemoryUsed string `json:"memory_used"` DiskUsed string `json:"disk_used"` Country string `json:"country"` - CountryISO string `json:"country_iso"` } type NodeCountries struct { - Countries map[string]string `json:"countries"` - CountryISOs map[string]string `json:"country_isos"` - Count int `json:"count"` + Countries map[string]string `json:"countries"` + Count int `json:"count"` } type NodeInfo struct { @@ -252,6 +246,7 @@ func main() { for i, port := range ports { url := *proxyBase + port proxies = append(proxies, fetchProxyInfo(fmt.Sprintf("proxy-%d", i+1), url)) + proxies = append(proxies, fetchProxyInfo(fmt.Sprintf("proxy-%d", i+1), url)) } } else if *proxyURLs != "" { urls := strings.Split(*proxyURLs, ",") From b5488e7ba6b153d9cbd253bca480937f8c71d508 Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Fri, 16 Jan 2026 13:49:32 +0530 Subject: [PATCH 3/3] refactor: improve network dashboard tool --- grpc_p2p_client/go.mod | 2 +- grpc_proxy_client/go.mod | 2 +- keygen/go.mod | 2 +- tools/network-dashboard/main.go | 67 ++++++++++++++++++--------------- 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/grpc_p2p_client/go.mod b/grpc_p2p_client/go.mod index 15a190f..23bd3e3 100644 --- a/grpc_p2p_client/go.mod +++ b/grpc_p2p_client/go.mod @@ -1,6 +1,6 @@ module p2p_client -go 1.24.1 +go 1.25.5 require ( github.com/gogo/protobuf v1.3.2 diff --git a/grpc_proxy_client/go.mod b/grpc_proxy_client/go.mod index 2b450ef..cc24728 100644 --- a/grpc_proxy_client/go.mod +++ b/grpc_proxy_client/go.mod @@ -1,6 +1,6 @@ module proxy_client -go 1.24.1 +go 1.25.5 require ( google.golang.org/grpc v1.73.0 diff --git a/keygen/go.mod b/keygen/go.mod index ab25a9e..c01601c 100644 --- a/keygen/go.mod +++ b/keygen/go.mod @@ -1,6 +1,6 @@ module p2p-keygen -go 1.24.1 +go 1.25.5 require ( github.com/benbjohnson/clock v1.3.0 // indirect diff --git a/tools/network-dashboard/main.go b/tools/network-dashboard/main.go index 13c99dc..57ea6b1 100644 --- a/tools/network-dashboard/main.go +++ b/tools/network-dashboard/main.go @@ -56,9 +56,10 @@ type ProxyInfo struct { Error string } +var httpClient = &http.Client{Timeout: 5 * time.Second} + func fetchJSON(url string, target interface{}) error { - client := &http.Client{Timeout: 5 * time.Second} - resp, err := client.Get(url) + resp, err := httpClient.Get(url) if err != nil { return err } @@ -212,20 +213,13 @@ func printDashboard(nodes []NodeInfo, proxies []ProxyInfo, nodeCountries *NodeCo fmt.Println(strings.Repeat("=", 100)) } -func min(a, b int) int { - if a < b { - return a - } - return b -} - func main() { var ( - proxyURLs = flag.String("proxies", "", "Comma-separated list of proxy URLs (e.g., http://localhost:8081,http://localhost:8082)") - nodeURLs = flag.String("nodes", "", "Comma-separated list of node URLs (e.g., http://localhost:9091,http://localhost:9092)") - proxyBase = flag.String("proxy-base", "", "Base URL for proxies (e.g., http://localhost) - will append :8081,:8082") - nodeBase = flag.String("node-base", "", "Base URL for nodes (e.g., http://localhost) - will append :9091,:9092,:9093,:9094") - local = flag.Bool("local", false, "Use localhost defaults (proxies: 8081,8082; nodes: 9091-9094)") + proxyURLsFlag = flag.String("proxies", "", "Comma-separated list of proxy URLs (e.g., http://localhost:8081,http://localhost:8082)") + nodeURLsFlag = flag.String("nodes", "", "Comma-separated list of node URLs (e.g., http://localhost:9091,http://localhost:9092)") + proxyBase = flag.String("proxy-base", "", "IP(s) or URL(s) for remote proxies - will prepend http:// and append :8080") + nodeBase = flag.String("node-base", "", "IP(s) or URL(s) for remote nodes (optional) - will prepend http:// and append :8081") + local = flag.Bool("local", false, "Use localhost defaults (proxies: 8081,8082; nodes: 9091-9094)") ) flag.Parse() @@ -233,23 +227,29 @@ func main() { var nodes []NodeInfo if *local { - proxyURLs := []string{"http://localhost:8081", "http://localhost:8082"} - for i, url := range proxyURLs { + proxyAddrs := []string{"http://localhost:8081", "http://localhost:8082"} + for i, url := range proxyAddrs { proxies = append(proxies, fetchProxyInfo(fmt.Sprintf("proxy-%d", i+1), url)) } - nodeURLs := []string{"http://localhost:9091", "http://localhost:9092", "http://localhost:9093", "http://localhost:9094"} - for i, url := range nodeURLs { + nodeAddrs := []string{"http://localhost:9091", "http://localhost:9092", "http://localhost:9093", "http://localhost:9094"} + for i, url := range nodeAddrs { nodes = append(nodes, fetchNodeInfo(fmt.Sprintf("p2pnode-%d", i+1), url)) } } else if *proxyBase != "" { - ports := []string{":8080"} - for i, port := range ports { - url := *proxyBase + port - proxies = append(proxies, fetchProxyInfo(fmt.Sprintf("proxy-%d", i+1), url)) + bases := strings.Split(*proxyBase, ",") + for i, base := range bases { + base = strings.TrimSpace(base) + if base == "" { + continue + } + if !strings.HasPrefix(base, "http://") && !strings.HasPrefix(base, "https://") { + base = "http://" + base + } + url := base + ":8080" proxies = append(proxies, fetchProxyInfo(fmt.Sprintf("proxy-%d", i+1), url)) } - } else if *proxyURLs != "" { - urls := strings.Split(*proxyURLs, ",") + } else if *proxyURLsFlag != "" { + urls := strings.Split(*proxyURLsFlag, ",") for i, url := range urls { url = strings.TrimSpace(url) if url == "" { @@ -260,13 +260,20 @@ func main() { } if *nodeBase != "" { - ports := []string{":8081"} - for i, port := range ports { - url := *nodeBase + port + bases := strings.Split(*nodeBase, ",") + for i, base := range bases { + base = strings.TrimSpace(base) + if base == "" { + continue + } + if !strings.HasPrefix(base, "http://") && !strings.HasPrefix(base, "https://") { + base = "http://" + base + } + url := base + ":8081" nodes = append(nodes, fetchNodeInfo(fmt.Sprintf("p2pnode-%d", i+1), url)) } - } else if *nodeURLs != "" { - urls := strings.Split(*nodeURLs, ",") + } else if *nodeURLsFlag != "" { + urls := strings.Split(*nodeURLsFlag, ",") for i, url := range urls { url = strings.TrimSpace(url) if url == "" { @@ -277,7 +284,7 @@ func main() { } if len(proxies) == 0 && len(nodes) == 0 { - fmt.Fprintf(os.Stderr, "Error: No proxies or nodes specified. Use -local, -proxy-base/-node-base, or -proxies/-nodes flags.\n") + fmt.Fprintf(os.Stderr, "Error: No proxies or nodes specified. Use -local, -proxy-base, or -proxies/-nodes flags.\n") flag.Usage() os.Exit(1) }