blob: ba4e6c2fda8c1773fa4d193c30d3dede3aad761e [file]
package cmd
import (
"fmt"
guiapp "github.com/apache/dubbo-kubernetes/dubbod/gui"
"github.com/spf13/cobra"
"net/http"
"net/url"
"os/exec"
"path"
"runtime"
"strings"
"time"
)
type guiArgs struct {
address string
path string
open bool
wait time.Duration
}
func GuiCmd() *cobra.Command {
args := &guiArgs{
address: "http://127.0.0.1:26080",
path: "/gui",
open: true,
wait: 30 * time.Second,
}
command := &cobra.Command{
Use: "gui",
Short: "Open the embedded dubbod GUI",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
guiURL, overviewURL, err := buildGUIURLs(args.address, args.path)
if err != nil {
return err
}
if err := waitForOverview(overviewURL, args.wait); err != nil {
return err
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), guiURL)
if args.open {
if err := openBrowser(guiURL); err != nil {
return err
}
}
return nil
},
}
flags := command.Flags()
flags.StringVar(&args.address, "address", args.address, "Base HTTP address for dubbod")
flags.StringVar(&args.path, "path", args.path, "GUI base path on the dubbod HTTP address")
flags.BoolVar(&args.open, "open", args.open, "Open the GUI in the default browser")
flags.DurationVar(&args.wait, "wait", args.wait, "Maximum time to wait for the GUI overview endpoint")
return command
}
func buildGUIURLs(address, guiPath string) (string, string, error) {
baseAddress := strings.TrimSpace(address)
if baseAddress == "" {
return "", "", fmt.Errorf("gui address cannot be empty")
}
if !strings.Contains(baseAddress, "://") {
baseAddress = "http://" + baseAddress
}
baseURL, err := url.Parse(baseAddress)
if err != nil {
return "", "", fmt.Errorf("invalid gui address %q: %w", address, err)
}
normalizedPath := guiapp.NormalizeBasePath(guiPath)
guiURL := *baseURL
guiURL.Path = path.Join("/", strings.TrimPrefix(baseURL.Path, "/"), strings.TrimPrefix(normalizedPath, "/"))
if normalizedPath != "/" && !strings.HasSuffix(guiURL.Path, "/") {
guiURL.Path += "/"
}
overviewURL := *baseURL
overviewURL.Path = path.Join(guiURL.Path, "api/overview")
return guiURL.String(), overviewURL.String(), nil
}
func waitForOverview(overviewURL string, timeout time.Duration) error {
client := &http.Client{
Timeout: 2 * time.Second,
}
deadline := time.Now().Add(timeout)
for {
response, err := client.Get(overviewURL)
if err == nil {
_ = response.Body.Close()
if response.StatusCode == http.StatusOK {
return nil
}
}
if timeout <= 0 || time.Now().After(deadline) {
return fmt.Errorf("gui overview endpoint is unavailable: %s", overviewURL)
}
time.Sleep(500 * time.Millisecond)
}
}
func openBrowser(target string) error {
var command *exec.Cmd
switch runtime.GOOS {
case "darwin":
command = exec.Command("open", target)
case "windows":
command = exec.Command("cmd", "/c", "start", "", target)
default:
command = exec.Command("xdg-open", target)
}
if err := command.Start(); err != nil {
return fmt.Errorf("failed to open browser for %s: %w", target, err)
}
return nil
}