diff --git a/.gitignore b/.gitignore index e43b0f9..04d12c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,20 @@ .DS_Store + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Log files +*.log + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/PMPC-NetworkTester b/PMPC-NetworkTester new file mode 100755 index 0000000..40b1666 Binary files /dev/null and b/PMPC-NetworkTester differ diff --git a/go.mod b/go.mod index e1e8885..9ef8704 100644 --- a/go.mod +++ b/go.mod @@ -2,3 +2,4 @@ module github.com/PatchMyPCTeam/PMPC-NetworkTester go 1.19 +require github.com/gorilla/websocket v1.5.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..25a9fc4 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/gui.go b/gui.go new file mode 100644 index 0000000..26f968a --- /dev/null +++ b/gui.go @@ -0,0 +1,523 @@ +package main + +import ( + "encoding/json" + "fmt" + "html/template" + "log" + "net" + "net/http" + "sync" + "time" + + "github.com/gorilla/websocket" + "github.com/PatchMyPCTeam/PMPC-NetworkTester/packages/downloadFile" + "github.com/PatchMyPCTeam/PMPC-NetworkTester/packages/goCMTrace" +) + +type WebGUI struct { + clients map[*websocket.Conn]bool + clientsMux sync.RWMutex + upgrader websocket.Upgrader + results []connectionResult + resultsMux sync.RWMutex +} + +type ProgressUpdate struct { + Type string `json:"type"` + Current int `json:"current"` + Total int `json:"total"` + Status string `json:"status"` + Progress float64 `json:"progress"` +} + +type TestResult struct { + Type string `json:"type"` + Result connectionResult `json:"result"` +} + +func NewWebGUI() *WebGUI { + return &WebGUI{ + clients: make(map[*websocket.Conn]bool), + upgrader: websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true // Allow connections from any origin + }, + }, + results: make([]connectionResult, 0), + } +} + +func (w *WebGUI) broadcast(message interface{}) { + w.clientsMux.RLock() + defer w.clientsMux.RUnlock() + + data, err := json.Marshal(message) + if err != nil { + return + } + + for client := range w.clients { + err := client.WriteMessage(websocket.TextMessage, data) + if err != nil { + delete(w.clients, client) + client.Close() + } + } +} + +func (w *WebGUI) handleWebSocket(rw http.ResponseWriter, r *http.Request) { + conn, err := w.upgrader.Upgrade(rw, r, nil) + if err != nil { + log.Printf("WebSocket upgrade error: %v", err) + return + } + defer conn.Close() + + w.clientsMux.Lock() + w.clients[conn] = true + w.clientsMux.Unlock() + + // Send current results to new client + w.resultsMux.RLock() + for _, result := range w.results { + w.broadcast(TestResult{Type: "result", Result: result}) + } + w.resultsMux.RUnlock() + + // Keep connection alive + for { + _, _, err := conn.ReadMessage() + if err != nil { + w.clientsMux.Lock() + delete(w.clients, conn) + w.clientsMux.Unlock() + break + } + } +} + +func (w *WebGUI) addResult(result connectionResult) { + w.resultsMux.Lock() + w.results = append(w.results, result) + w.resultsMux.Unlock() + + w.broadcast(TestResult{Type: "result", Result: result}) +} + +func (w *WebGUI) updateProgress(current, total int, status string) { + var progress float64 + if total > 0 { + progress = float64(current) / float64(total) * 100 + } + + update := ProgressUpdate{ + Type: "progress", + Current: current, + Total: total, + Status: status, + Progress: progress, + } + + w.broadcast(update) +} + +func (w *WebGUI) connectionTestWeb(connection connectionInfo, wg *sync.WaitGroup) connectionResult { + defer wg.Done() + + // Duplicate the connectionTest logic to avoid WaitGroup conflicts + result := connectionResult{} + timeout := time.Second * 5 + conn, err := net.DialTimeout("tcp", net.JoinHostPort(connection.domainName, connection.port), timeout) + logObj := new(goCMTrace.LogEntry) + logObj.File = "PMPC-NetworkTester.log" + if err != nil { + result = connectionResult{ + product: connection.product, + domainName: connection.domainName, + port: connection.port, + reason: connection.reason, + result: "Failed", + err: err, + } + } + if conn != nil { + defer conn.Close() + result = connectionResult{ + product: connection.product, + domainName: connection.domainName, + port: connection.port, + reason: connection.reason, + result: "Success", + } + logMessage := "Successfully tested for Product: " + result.product + " for the reason: " + result.reason + " connected to: " + net.JoinHostPort(result.domainName, result.port) + logObj.Message = logMessage + logObj.State = 1 + goCMTrace.LogData(*logObj) + } + if result.result == "Failed" { + logMessage := "Failed test for Product: " + result.product + " to connect to: " + result.domainName + " " + result.port + " due to " + result.err.Error() + logObj.Message = logMessage + logObj.State = 3 + goCMTrace.LogData(*logObj) + } + + // Add result to GUI + w.addResult(result) + + return result +} + +func (w *WebGUI) runNetworkTests() { + var wg sync.WaitGroup + + w.updateProgress(0, 1, "Starting network tests...") + + // Test initial connection to Patch My PC + wg.Add(1) + connectionObj := connectionInfo{ + product: "Patch My PC Network Tester", + domainName: "patchmypc.com", + port: "443", + reason: "Base Functionality", + } + + state := w.connectionTestWeb(connectionObj, &wg) + wg.Wait() + + if state.result == "Success" { + w.updateProgress(1, 1, "Downloading domain list...") + + fileName, err := downloadFile.DownloadFile("https://patchmypc.com/scupcatalog/downloads/PatchMyPC-DomainList.csv") + if err != nil { + w.updateProgress(1, 1, fmt.Sprintf("Error downloading domain list: %v", err)) + return + } + + records, err := readData(fileName) + if err != nil { + w.updateProgress(1, 1, fmt.Sprintf("Error reading domain list: %v", err)) + return + } + + // Count valid connections + totalConnections := 0 + for _, record := range records { + if len(record) > 5 && shouldConnect(record[2]) { + totalConnections++ + } + } + + w.updateProgress(0, totalConnections, fmt.Sprintf("Testing %d connections...", totalConnections)) + + current := 0 + for _, record := range records { + if len(record) > 5 { + connectionObj := connectionInfo{ + product: record[1], + domainName: record[2], + port: record[4], + reason: record[5], + } + if shouldConnect(connectionObj.domainName) { + wg.Add(1) + current++ + w.updateProgress(current, totalConnections, fmt.Sprintf("Testing %s (%d/%d)", connectionObj.domainName, current, totalConnections)) + go w.connectionTestWeb(connectionObj, &wg) + } + } + } + + go func() { + wg.Wait() + w.updateProgress(totalConnections, totalConnections, "Network tests completed!") + }() + + } else { + w.updateProgress(1, 1, "Failed to connect to Patch My PC - Cannot progress further.") + } +} + +func (w *WebGUI) handleStart(rw http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + go w.runNetworkTests() + rw.WriteHeader(http.StatusOK) + rw.Write([]byte("Tests started")) + } +} + +const htmlTemplate = ` + + + + Patch My PC Network Tester + + + +
+
+ +

Network Tester

+
+ +
+ +
+
+
+
Ready to start network tests...
+
+ +
+
Test Results
+
+
+
0
+
Successful
+
+
+
0
+
Failed
+
+
+
+ +
+
+
+ + + + +` + +func (w *WebGUI) handleHome(rw http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.New("home").Parse(htmlTemplate)) + tmpl.Execute(rw, nil) +} + +func runGUI() { + webGUI := NewWebGUI() + + http.HandleFunc("/", webGUI.handleHome) + http.HandleFunc("/ws", webGUI.handleWebSocket) + http.HandleFunc("/start", webGUI.handleStart) + + fmt.Println("Starting Patch My PC Network Tester GUI...") + fmt.Println("Open your web browser and go to: http://localhost:8080") + fmt.Println("Press Ctrl+C to stop the server") + + server := &http.Server{ + Addr: ":8080", + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + } + + log.Fatal(server.ListenAndServe()) +} \ No newline at end of file diff --git a/main.go b/main.go index e36df23..3e95c0f 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "encoding/csv" + "flag" "fmt" "log" "net" @@ -95,6 +96,16 @@ type connectionResult struct { } func main() { + // Parse command line flags + guiMode := flag.Bool("gui", false, "Run in GUI mode") + flag.Parse() + + if *guiMode { + runGUI() + return + } + + // Original CLI mode var wg sync.WaitGroup wg.Add(1) connectionObj := connectionInfo{ diff --git a/readme.md b/readme.md index 7cffd1e..bc04357 100644 --- a/readme.md +++ b/readme.md @@ -8,8 +8,44 @@ In no event shall PMP or its employees be liable for any liability, loss, injury Patch My PC provides scripts, macro, and other code examples for illustration only, without warranty either expressed or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose. This script is provided 'AS IS' and Patch My PC does not guarantee that the following script, macro, or code can or should be used in any situation or that operation of the code will be error-free. +## Usage + +The Patch My PC Network Tester can be run in two modes: + +### Command Line Interface (CLI) Mode +Run the application without any flags for traditional console output: + +``` +PMPC-NetworkTester.exe +``` + +### Graphical User Interface (GUI) Mode +Run the application with the `-gui` flag to launch the web-based GUI: + +``` +PMPC-NetworkTester.exe -gui +``` + +This will start a web server on `http://localhost:8080`. Open your web browser and navigate to this address to access the GUI. The GUI features: + +- **Patch My PC Logo**: Prominently displayed branding +- **Progress Bar**: Real-time visual indication of test progress +- **Live Status Updates**: Current test status and progress information +- **Test Results**: Live updating list of connection test results with success/failure indicators +- **Statistics**: Summary counters for successful and failed tests + +The GUI works on Windows 10, Windows 11, and Windows Server without requiring additional dependencies. + ## Compile Instructions Windows +### CLI Version +```go +go build -o PMPC-NetworkTester.exe +``` + +### GUI Version (same executable, different usage) ```go go build -o PMPC-NetworkTester.exe -ldflags -H=windowsgui ``` + +The `-ldflags -H=windowsgui` flag is optional and only affects the console window behavior in GUI mode.