Skip to content

Commit f6b440b

Browse files
committed
Refactor application structure to implement service-oriented architecture
- Moved template management functions to TemplateService for better separation of concerns. - Created BatchImportService for handling log file imports. - Introduced ConnectionService for managing connection-related operations. - Added ProfileService to manage connection profiles. - Implemented StressTestService for continuous message sending operations. - Developed SyslogService for syslog message formatting and transmission. - Updated main application to initialize and inject services properly. - Enhanced context management for services to ensure proper lifecycle handling. - Added CRUD operations for log templates and connection profiles. - Improved error handling and logging throughout the services.
1 parent 7b426bb commit f6b440b

30 files changed

Lines changed: 1665 additions & 1394 deletions

app.go

Lines changed: 0 additions & 1257 deletions
This file was deleted.

app_core.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"context"
5+
6+
"github.com/wailsapp/wails/v2/pkg/runtime"
7+
)
8+
9+
// Version information - injected at build time via ldflags
10+
// Use: go build -ldflags "-X main.Version=1.4.2"
11+
var Version = "dev"
12+
13+
// ================================================================================
14+
// APP CORE - Main application struct with lifecycle management
15+
// Follows Single Responsibility Principle: handles only app lifecycle and metadata
16+
// ================================================================================
17+
18+
// App struct is the main application controller
19+
// It manages lifecycle hooks and coordinates between services
20+
type App struct {
21+
ctx context.Context
22+
23+
// Service references for coordination
24+
connectionService *ConnectionService
25+
syslogService *SyslogService
26+
stressTestService *StressTestService
27+
}
28+
29+
// NewApp creates a new App instance
30+
func NewApp() *App {
31+
return &App{}
32+
}
33+
34+
// SetServices injects service dependencies (called from main.go)
35+
func (a *App) SetServices(conn *ConnectionService, syslog *SyslogService, stress *StressTestService) {
36+
a.connectionService = conn
37+
a.syslogService = syslog
38+
a.stressTestService = stress
39+
}
40+
41+
// startup is called at application startup
42+
func (a *App) startup(ctx context.Context) {
43+
a.ctx = ctx
44+
}
45+
46+
// domReady is called after front-end resources have been loaded
47+
func (a *App) domReady(ctx context.Context) {
48+
// Center the window on the screen
49+
runtime.WindowCenter(a.ctx)
50+
}
51+
52+
// beforeClose is called when the application is about to quit
53+
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
54+
// Check if connection service has active connection
55+
if a.connectionService != nil && a.connectionService.IsConnected() {
56+
result, err := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
57+
Type: runtime.QuestionDialog,
58+
Title: "Confirm Exit",
59+
Message: "There is an active connection. Are you sure you want to exit?",
60+
Buttons: []string{"Yes", "No"},
61+
DefaultButton: "No",
62+
CancelButton: "No",
63+
})
64+
if err != nil || result != "Yes" {
65+
return true // Prevent close
66+
}
67+
}
68+
69+
// Clean up connections
70+
if a.connectionService != nil {
71+
a.connectionService.Disconnect()
72+
}
73+
74+
// Stop stress test if running
75+
if a.stressTestService != nil {
76+
a.stressTestService.StopContinuousSend()
77+
}
78+
79+
return false
80+
}
81+
82+
// shutdown is called at application termination
83+
func (a *App) shutdown(ctx context.Context) {
84+
// Ensure all connections are closed
85+
if a.connectionService != nil {
86+
a.connectionService.Disconnect()
87+
}
88+
}
89+
90+
// ================================================================================
91+
// APP METADATA API
92+
// ================================================================================
93+
94+
// GetVersion returns the application version
95+
// The version is injected at build time via ldflags
96+
func (a *App) GetVersion() string {
97+
return Version
98+
}
99+
100+
// OpenURL opens a URL in the default system browser
101+
func (a *App) OpenURL(url string) {
102+
runtime.BrowserOpenURL(a.ctx, url)
103+
runtime.LogDebug(a.ctx, "Opened URL in browser: "+url)
104+
}
105+
106+
// OpenGitHub opens the project's GitHub repository in the default browser
107+
func (a *App) OpenGitHub() {
108+
runtime.BrowserOpenURL(a.ctx, "https://github.com/yllada/Send-Log-TCP")
109+
}

batch_import.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/csv"
6+
"encoding/json"
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/wailsapp/wails/v2/pkg/runtime"
13+
)
14+
15+
// ================================================================================
16+
// BATCH IMPORT SERVICE - Handles log file imports (Enterprise Feature)
17+
// Follows Single Responsibility Principle: handles only batch import operations
18+
// ================================================================================
19+
20+
// BatchImportResult represents the result of a batch import operation
21+
type BatchImportResult struct {
22+
Messages []string `json:"messages"`
23+
TotalLines int `json:"totalLines"`
24+
Errors []string `json:"errors"`
25+
}
26+
27+
// BatchImportService handles batch log import operations
28+
type BatchImportService struct {
29+
ctx context.Context
30+
}
31+
32+
// NewBatchImportService creates a new BatchImportService instance
33+
func NewBatchImportService() *BatchImportService {
34+
return &BatchImportService{}
35+
}
36+
37+
// SetContext sets the Wails runtime context
38+
func (b *BatchImportService) SetContext(ctx context.Context) {
39+
b.ctx = ctx
40+
}
41+
42+
// SelectLogFile opens a file dialog to select a log file for batch import
43+
func (b *BatchImportService) SelectLogFile() (string, error) {
44+
filePath, err := runtime.OpenFileDialog(b.ctx, runtime.OpenDialogOptions{
45+
Title: "Select Log File for Import",
46+
Filters: []runtime.FileFilter{
47+
{
48+
DisplayName: "Log Files (*.csv, *.json, *.txt, *.log)",
49+
Pattern: "*.csv;*.json;*.txt;*.log",
50+
},
51+
{
52+
DisplayName: "CSV Files (*.csv)",
53+
Pattern: "*.csv",
54+
},
55+
{
56+
DisplayName: "JSON Files (*.json)",
57+
Pattern: "*.json",
58+
},
59+
{
60+
DisplayName: "Text Files (*.txt, *.log)",
61+
Pattern: "*.txt;*.log",
62+
},
63+
{
64+
DisplayName: "All Files (*.*)",
65+
Pattern: "*.*",
66+
},
67+
},
68+
})
69+
if err != nil {
70+
return "", fmt.Errorf("failed to open file dialog: %w", err)
71+
}
72+
return filePath, nil
73+
}
74+
75+
// ImportLogFile reads and parses a log file, returning the messages
76+
func (b *BatchImportService) ImportLogFile(filePath string) (BatchImportResult, error) {
77+
result := BatchImportResult{
78+
Messages: []string{},
79+
Errors: []string{},
80+
}
81+
82+
// Read file content
83+
content, err := os.ReadFile(filePath)
84+
if err != nil {
85+
return result, fmt.Errorf("failed to read file: %w", err)
86+
}
87+
88+
// Detect format based on extension
89+
ext := strings.ToLower(filepath.Ext(filePath))
90+
91+
switch ext {
92+
case ".json":
93+
return b.parseJSONLogs(content)
94+
case ".csv":
95+
return b.parseCSVLogs(content)
96+
default:
97+
// Try JSON first, then fallback to plain text
98+
if len(content) > 0 && (content[0] == '[' || content[0] == '{') {
99+
jsonResult, err := b.parseJSONLogs(content)
100+
if err == nil && len(jsonResult.Messages) > 0 {
101+
return jsonResult, nil
102+
}
103+
}
104+
return b.parseTextLogs(content)
105+
}
106+
}
107+
108+
// parseJSONLogs parses JSON format logs
109+
func (b *BatchImportService) parseJSONLogs(content []byte) (BatchImportResult, error) {
110+
result := BatchImportResult{
111+
Messages: []string{},
112+
Errors: []string{},
113+
}
114+
115+
// Try parsing as array of strings
116+
var stringArray []string
117+
if err := json.Unmarshal(content, &stringArray); err == nil {
118+
for _, msg := range stringArray {
119+
if trimmed := strings.TrimSpace(msg); trimmed != "" {
120+
result.Messages = append(result.Messages, trimmed)
121+
}
122+
}
123+
result.TotalLines = len(stringArray)
124+
return result, nil
125+
}
126+
127+
// Try parsing as array of objects with "message" field
128+
var objArray []map[string]interface{}
129+
if err := json.Unmarshal(content, &objArray); err == nil {
130+
result.TotalLines = len(objArray)
131+
for i, obj := range objArray {
132+
if msg, ok := obj["message"].(string); ok && strings.TrimSpace(msg) != "" {
133+
result.Messages = append(result.Messages, strings.TrimSpace(msg))
134+
} else if msg, ok := obj["msg"].(string); ok && strings.TrimSpace(msg) != "" {
135+
result.Messages = append(result.Messages, strings.TrimSpace(msg))
136+
} else if msg, ok := obj["log"].(string); ok && strings.TrimSpace(msg) != "" {
137+
result.Messages = append(result.Messages, strings.TrimSpace(msg))
138+
} else {
139+
result.Errors = append(result.Errors, fmt.Sprintf("Line %d: no 'message', 'msg', or 'log' field found", i+1))
140+
}
141+
}
142+
return result, nil
143+
}
144+
145+
// Try parsing as newline-delimited JSON (NDJSON)
146+
lines := strings.Split(string(content), "\n")
147+
result.TotalLines = len(lines)
148+
for i, line := range lines {
149+
line = strings.TrimSpace(line)
150+
if line == "" {
151+
continue
152+
}
153+
var obj map[string]interface{}
154+
if err := json.Unmarshal([]byte(line), &obj); err == nil {
155+
if msg, ok := obj["message"].(string); ok && strings.TrimSpace(msg) != "" {
156+
result.Messages = append(result.Messages, strings.TrimSpace(msg))
157+
} else if msg, ok := obj["msg"].(string); ok && strings.TrimSpace(msg) != "" {
158+
result.Messages = append(result.Messages, strings.TrimSpace(msg))
159+
} else {
160+
result.Errors = append(result.Errors, fmt.Sprintf("Line %d: no message field found", i+1))
161+
}
162+
} else {
163+
result.Errors = append(result.Errors, fmt.Sprintf("Line %d: invalid JSON", i+1))
164+
}
165+
}
166+
167+
return result, nil
168+
}
169+
170+
// parseCSVLogs parses CSV format logs
171+
func (b *BatchImportService) parseCSVLogs(content []byte) (BatchImportResult, error) {
172+
result := BatchImportResult{
173+
Messages: []string{},
174+
Errors: []string{},
175+
}
176+
177+
reader := csv.NewReader(strings.NewReader(string(content)))
178+
reader.TrimLeadingSpace = true
179+
reader.LazyQuotes = true
180+
181+
records, err := reader.ReadAll()
182+
if err != nil {
183+
return result, fmt.Errorf("failed to parse CSV: %w", err)
184+
}
185+
186+
if len(records) == 0 {
187+
return result, nil
188+
}
189+
190+
result.TotalLines = len(records)
191+
192+
// Check for header row
193+
messageColIdx := 0
194+
hasHeader := false
195+
header := records[0]
196+
197+
for i, col := range header {
198+
colLower := strings.ToLower(strings.TrimSpace(col))
199+
if colLower == "message" || colLower == "msg" || colLower == "log" {
200+
messageColIdx = i
201+
hasHeader = true
202+
break
203+
}
204+
}
205+
206+
startRow := 0
207+
if hasHeader {
208+
startRow = 1
209+
}
210+
211+
for i := startRow; i < len(records); i++ {
212+
record := records[i]
213+
if len(record) > messageColIdx {
214+
msg := strings.TrimSpace(record[messageColIdx])
215+
if msg != "" {
216+
result.Messages = append(result.Messages, msg)
217+
}
218+
} else {
219+
result.Errors = append(result.Errors, fmt.Sprintf("Line %d: insufficient columns", i+1))
220+
}
221+
}
222+
223+
return result, nil
224+
}
225+
226+
// parseTextLogs parses plain text logs (one message per line)
227+
func (b *BatchImportService) parseTextLogs(content []byte) (BatchImportResult, error) {
228+
result := BatchImportResult{
229+
Messages: []string{},
230+
Errors: []string{},
231+
}
232+
233+
lines := strings.Split(string(content), "\n")
234+
result.TotalLines = len(lines)
235+
236+
for _, line := range lines {
237+
trimmed := strings.TrimSpace(line)
238+
if trimmed != "" {
239+
result.Messages = append(result.Messages, trimmed)
240+
}
241+
}
242+
243+
return result, nil
244+
}

0 commit comments

Comments
 (0)