mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-08-24 07:55:06 +03:00
Refactor web UI server setup in main.go and update default host in config
This commit is contained in:
parent
345d5b9cbd
commit
51a1a0a3d7
9 changed files with 146 additions and 55 deletions
|
@ -264,23 +264,6 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
// Set up the web UI server if enabled in config.
|
||||
if cfg.WebUI.Enable {
|
||||
var listenAddr string
|
||||
if cfg.WebUI.Host == "" {
|
||||
listenAddr = fmt.Sprintf(":%d", cfg.WebUI.Port)
|
||||
} else {
|
||||
listenAddr = fmt.Sprintf("%s:%d", cfg.WebUI.Host, cfg.WebUI.Port)
|
||||
}
|
||||
|
||||
n.webui = webui.Server(listenAddr, logger)
|
||||
go func() {
|
||||
if err := n.webui.Start(); err != nil {
|
||||
logger.Errorf("WebUI server error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Set up the multicast module.
|
||||
{
|
||||
options := []multicast.SetupOption{}
|
||||
|
@ -316,6 +299,23 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
// Set up the web UI server if enabled in config.
|
||||
if cfg.WebUI.Enable {
|
||||
var listenAddr string
|
||||
if cfg.WebUI.Host == "" {
|
||||
listenAddr = fmt.Sprintf(":%d", cfg.WebUI.Port)
|
||||
} else {
|
||||
listenAddr = fmt.Sprintf("%s:%d", cfg.WebUI.Host, cfg.WebUI.Port)
|
||||
}
|
||||
|
||||
n.webui = webui.Server(listenAddr, logger)
|
||||
go func() {
|
||||
if err := n.webui.Start(); err != nil {
|
||||
logger.Errorf("WebUI server error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//Windows service shutdown
|
||||
minwinsvc.SetOnExit(func() {
|
||||
logger.Infof("Shutting down service ...")
|
||||
|
|
|
@ -96,7 +96,7 @@ func GenerateConfig() *NodeConfig {
|
|||
cfg.WebUI = WebUIConfig{
|
||||
Enable: false,
|
||||
Port: 9000,
|
||||
Host: "",
|
||||
Host: "127.0.0.1",
|
||||
}
|
||||
if err := cfg.postprocessConfig(); err != nil {
|
||||
panic(err)
|
||||
|
|
108
src/webui/README.md
Normal file
108
src/webui/README.md
Normal file
|
@ -0,0 +1,108 @@
|
|||
# WebUI Module
|
||||
|
||||
This module provides a web interface for managing Yggdrasil node through a browser.
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ HTTP web server with static files
|
||||
- ✅ Health check endpoint (`/health`)
|
||||
- ✅ Development and production build modes
|
||||
- ✅ Automatic binding to Yggdrasil IPv6 address
|
||||
- ✅ IPv4 and IPv6 support
|
||||
- ✅ Path traversal attack protection
|
||||
|
||||
## Configuration
|
||||
|
||||
In the Yggdrasil configuration file:
|
||||
|
||||
```json
|
||||
{
|
||||
"WebUI": {
|
||||
"Enable": true,
|
||||
"Port": 9000,
|
||||
"Host": "",
|
||||
"BindYgg": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration parameters:
|
||||
|
||||
- **`Enable`** - enable/disable WebUI
|
||||
- **`Port`** - port for web interface (default 9000)
|
||||
- **`Host`** - IP address to bind to (empty means all interfaces)
|
||||
- **`BindYgg`** - automatically bind to Yggdrasil IPv6 address
|
||||
|
||||
## Usage
|
||||
|
||||
### Standard mode
|
||||
|
||||
```go
|
||||
server := webui.Server("127.0.0.1:9000", logger)
|
||||
```
|
||||
|
||||
### With core access
|
||||
|
||||
```go
|
||||
server := webui.ServerWithCore("127.0.0.1:9000", logger, coreInstance)
|
||||
```
|
||||
|
||||
### Automatic Yggdrasil address binding
|
||||
|
||||
```go
|
||||
server := webui.ServerForYggdrasil(9000, logger, coreInstance)
|
||||
// Automatically binds to [yggdrasil_ipv6]:9000
|
||||
```
|
||||
|
||||
### Starting the server
|
||||
|
||||
```go
|
||||
go func() {
|
||||
if err := server.Start(); err != nil {
|
||||
logger.Errorf("WebUI server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// To stop
|
||||
server.Stop()
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
- **`/`** - main page (index.html)
|
||||
- **`/health`** - health check (returns "OK")
|
||||
- **`/static/*`** - static files (CSS, JS, images)
|
||||
|
||||
## Build modes
|
||||
|
||||
### Development mode (`-tags debug`)
|
||||
- Files loaded from disk from `src/webui/static/`
|
||||
- File changes available without rebuild
|
||||
|
||||
### Production mode (default)
|
||||
- Files embedded in binary
|
||||
- Faster loading, smaller deployment size
|
||||
|
||||
## Security
|
||||
|
||||
- Path traversal attack protection
|
||||
- Configured HTTP timeouts
|
||||
- Header size limits
|
||||
- File MIME type validation
|
||||
|
||||
## Testing
|
||||
|
||||
The module includes a comprehensive test suite:
|
||||
|
||||
```bash
|
||||
cd src/webui
|
||||
go test -v
|
||||
```
|
||||
|
||||
Tests cover:
|
||||
- Server creation and management
|
||||
- HTTP endpoints
|
||||
- Static files (dev and prod modes)
|
||||
- Error handling
|
||||
- Configuration
|
||||
- Yggdrasil IPv6 binding
|
|
@ -164,11 +164,11 @@ func TestWebUIConfig_PortRanges(t *testing.T) {
|
|||
if test.shouldWork {
|
||||
// Try to start briefly to see if port is valid
|
||||
go func() {
|
||||
server.Start()
|
||||
_ = server.Start()
|
||||
}()
|
||||
|
||||
// Quick cleanup
|
||||
server.Stop()
|
||||
_ = server.Stop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -236,9 +236,9 @@ func TestWebUIConfig_Integration(t *testing.T) {
|
|||
|
||||
// Test that server can start with this config
|
||||
go func() {
|
||||
server.Start()
|
||||
_ = server.Start()
|
||||
}()
|
||||
defer server.Stop()
|
||||
defer func() { _ = server.Stop() }()
|
||||
|
||||
// Verify server properties match config
|
||||
if server.listen != listenAddr {
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestWebUIServer_HealthEndpointDetails(t *testing.T) {
|
|||
})
|
||||
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("OK"))
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
|
@ -109,7 +109,7 @@ func TestWebUIServer_NonExistentEndpoint(t *testing.T) {
|
|||
})
|
||||
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("OK"))
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
|
@ -158,7 +158,7 @@ func TestWebUIServer_ContentTypes(t *testing.T) {
|
|||
})
|
||||
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("OK"))
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
|
@ -188,7 +188,7 @@ func TestWebUIServer_HeaderSecurity(t *testing.T) {
|
|||
})
|
||||
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("OK"))
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
|
@ -227,7 +227,7 @@ func TestWebUIServer_ConcurrentRequests(t *testing.T) {
|
|||
})
|
||||
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("OK"))
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
|
|
|
@ -30,7 +30,7 @@ func TestWebUIServer_InvalidListenAddress(t *testing.T) {
|
|||
// Start should fail for invalid addresses
|
||||
err := server.Start()
|
||||
if err == nil {
|
||||
server.Stop() // Clean up if it somehow started
|
||||
_ = server.Stop() // Clean up if it somehow started
|
||||
t.Errorf("Expected Start() to fail for invalid address %s", addr)
|
||||
}
|
||||
})
|
||||
|
@ -56,7 +56,7 @@ func TestWebUIServer_PortAlreadyInUse(t *testing.T) {
|
|||
// This should fail because port is already in use
|
||||
err = server.Start()
|
||||
if err == nil {
|
||||
server.Stop()
|
||||
_ = server.Stop()
|
||||
t.Error("Expected Start() to fail when port is already in use")
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ func TestWebUIServer_StopTwice(t *testing.T) {
|
|||
|
||||
// Start server
|
||||
go func() {
|
||||
server.Start()
|
||||
_ = server.Start()
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
@ -218,7 +218,7 @@ func TestWebUIServer_ContextCancellation(t *testing.T) {
|
|||
|
||||
// Start server
|
||||
go func() {
|
||||
server.Start()
|
||||
_ = server.Start()
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
@ -255,7 +255,7 @@ func TestWebUIServer_EmptyListenAddress(t *testing.T) {
|
|||
// This might fail when trying to start
|
||||
err := server.Start()
|
||||
if err == nil {
|
||||
server.Stop()
|
||||
_ = server.Stop()
|
||||
t.Log("Note: Server started with empty listen address")
|
||||
} else {
|
||||
t.Logf("Expected behavior: Start() failed with empty address: %v", err)
|
||||
|
@ -308,7 +308,7 @@ func TestWebUIServer_LargeNumberOfRequests(t *testing.T) {
|
|||
})
|
||||
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("OK"))
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
|
|
|
@ -35,7 +35,7 @@ func (w *WebUIServer) Start() error {
|
|||
// Health check endpoint
|
||||
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("OK"))
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
w.server = &http.Server{
|
||||
|
|
|
@ -58,7 +58,7 @@ func serveFile(rw http.ResponseWriter, r *http.Request, log core.Logger) {
|
|||
|
||||
// Set headers and serve the file
|
||||
rw.Header().Set("Content-Type", contentType)
|
||||
rw.Write(data)
|
||||
_, _ = rw.Write(data)
|
||||
|
||||
log.Debugf("Served file: %s (type: %s)", filePath, contentType)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package webui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -23,22 +22,6 @@ func getTestAddress() string {
|
|||
return "127.0.0.1:0" // Let OS assign available port
|
||||
}
|
||||
|
||||
// Helper function to wait for server to be ready
|
||||
func waitForServer(url string, timeout time.Duration) error {
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
deadline := time.Now().Add(timeout)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
resp, err := client.Get(url)
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("server not ready within timeout")
|
||||
}
|
||||
|
||||
func TestWebUIServer_Creation(t *testing.T) {
|
||||
logger := createTestLogger()
|
||||
listen := getTestAddress()
|
||||
|
@ -123,7 +106,7 @@ func TestWebUIServer_HealthEndpoint(t *testing.T) {
|
|||
})
|
||||
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("OK"))
|
||||
_, _ = rw.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
|
@ -156,9 +139,9 @@ func TestWebUIServer_Timeouts(t *testing.T) {
|
|||
|
||||
// Start server
|
||||
go func() {
|
||||
server.Start()
|
||||
_ = server.Start()
|
||||
}()
|
||||
defer server.Stop()
|
||||
defer func() { _ = server.Stop() }()
|
||||
|
||||
// Wait for server to start
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue