Skip to content

Commit

Permalink
Merge pull request #73 from launchrctl/start-stop-ui
Browse files Browse the repository at this point in the history
Web - Start/stop UX, background option
  • Loading branch information
davidferlay authored Aug 26, 2024
2 parents 5904081 + 9a25eb6 commit 79486b9
Show file tree
Hide file tree
Showing 10 changed files with 624 additions and 66 deletions.
2 changes: 1 addition & 1 deletion client/src/utils/app-urls-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const isProductionMode = import.meta.env.MODE === 'production'
const addDefaultPort = (protocol: string) =>
protocol === 'https:' ? ':433' : ':80'
protocol === 'https:' ? ':443' : ':80'

export function getApiUrl() {
let url = import.meta.env.VITE_API_URL
Expand Down
3 changes: 2 additions & 1 deletion files.dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ package web

import (
"embed"
"github.com/launchrctl/web/server"
"io/fs"

"github.com/launchrctl/web/server"
)

//go:embed swagger-ui/*
Expand Down
77 changes: 56 additions & 21 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ package web

import (
"fmt"
"path/filepath"

"github.com/launchrctl/launchr"
"github.com/launchrctl/web/server"
"github.com/spf13/cobra"
)

// APIPrefix is a default api prefix on the server.
const APIPrefix = "/api"
const (
pluginName = "web"

// APIPrefix is a default api prefix on the server.
APIPrefix = "/api"

stopArg = "stop"
)

func init() {
launchr.RegisterPlugin(&Plugin{})
Expand All @@ -19,6 +25,7 @@ func init() {
// Plugin is launchr plugin providing web ui.
type Plugin struct {
app launchr.App
cfg launchr.Config
}

// PluginInfo implements launchr.Plugin interface.
Expand All @@ -28,41 +35,69 @@ func (p *Plugin) PluginInfo() launchr.PluginInfo {

// OnAppInit implements launchr.Plugin interface.
func (p *Plugin) OnAppInit(app launchr.App) error {
app.GetService(&p.cfg)

p.app = app
return nil
}

type webFlags struct {
Port int
ProxyClient string
UseSwaggerUI bool
RunInfoDir string
}

// CobraAddCommands implements launchr.CobraPlugin interface to provide web functionality.
func (p *Plugin) CobraAddCommands(rootCmd *cobra.Command) error {
// Flag options.
var port string
var proxyClient string
var useSwaggerUI bool
pluginTmpDir := p.getPluginTempDir()
webPidFile := filepath.Join(pluginTmpDir, "web.pid")

webRunFlags := webFlags{
RunInfoDir: pluginTmpDir,
}

var foreground bool
var cmd = &cobra.Command{
Use: "web",
Short: "Starts web server",
Aliases: []string{"ui"},
Use: "web [stop]",
Short: "Starts web server",
Args: cobra.MatchAll(cobra.RangeArgs(0, 1), cobra.OnlyValidArgs),
ValidArgs: []string{stopArg},
Aliases: []string{"ui"},
Example: `web
web --foreground
web stop`,
RunE: func(cmd *cobra.Command, args []string) error {
// Don't show usage help on a runtime error.
cmd.SilenceUsage = true

runOpts := &server.RunOptions{
Addr: fmt.Sprintf(":%s", port), // @todo use proper addr
APIPrefix: APIPrefix,
SwaggerJSON: useSwaggerUI,
ProxyClient: proxyClient,
// @todo use embed fs for client or provide path ?
// If 'stop' arg passed, try to interrupt process and remove PID file.
if len(args) > 0 && args[0] == stopArg {
return stopWeb(webPidFile, webRunFlags.RunInfoDir)
}

if ok, url := isWebRunning(webPidFile, webRunFlags.RunInfoDir); ok {
return fmt.Errorf("another server is already running at the URL: %s. please stop the existing server before starting a new one", url)
}

prepareRunOption(p, runOpts)
if foreground {
//@TODO refactor to pass only plugin.app instead of full plugin.
return runWeb(cmd.Context(), p, &webRunFlags)
}

return server.Run(cmd.Context(), p.app, runOpts)
return runBackgroundWeb(cmd, p, &webRunFlags, webPidFile)
},
}
cmd.Flags().StringVarP(&port, "port", "p", "8080", `Web server port`)
cmd.Flags().BoolVarP(&useSwaggerUI, "swagger-ui", "", false, `Serve swagger.json on /api/swagger.json and Swagger UI on /api/swagger-ui`)
cmd.Flags().StringVarP(&proxyClient, "proxy-client", "", "", `Proxies to client web server, useful in local development`)

cmd.Flags().IntVarP(&webRunFlags.Port, "port", "p", 8080, `Web server port`)
cmd.Flags().BoolVarP(&webRunFlags.UseSwaggerUI, "swagger-ui", "", false, `Serve swagger.json on /api/swagger.json and Swagger UI on /api/swagger-ui`)
cmd.Flags().StringVarP(&webRunFlags.ProxyClient, "proxy-client", "", "", `Proxies to client web server, useful in local development`)
cmd.Flags().BoolVarP(&foreground, "foreground", "", false, `Run server as foreground process`)
// Command flags.
rootCmd.AddCommand(cmd)
return nil
}

func (p *Plugin) getPluginTempDir() string {
return p.cfg.Path(pluginName)
}
37 changes: 37 additions & 0 deletions process-unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build !windows
// +build !windows

package web

import (
"os"
"os/exec"
"syscall"

"github.com/launchrctl/launchr/pkg/log"
)

func setSysProcAttr(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true,
}
}

func isProcessRunning(pid int) bool {
process, err := os.FindProcess(pid)
if err != nil {
log.Debug("Failed to find process: %s\n", err)
return false
}

err = process.Signal(syscall.Signal(0))
if err == nil {
return true
}

if err.Error() == "os: process already finished" {
return false
}

return true
}
35 changes: 35 additions & 0 deletions process-win.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//go:build windows
// +build windows

package web

import (
"os/exec"

"golang.org/x/sys/windows"
)

func setSysProcAttr(cmd *exec.Cmd) {
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.CREATE_NEW_PROCESS_GROUP,
}
}

func isProcessRunning(pid uint32) bool {
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
if err != nil {
return false
}
defer windows.CloseHandle(h)

var code uint32
err = windows.GetExitCodeProcess(h, &code)
if err != nil {
return false
}
if code == windows.STILL_ACTIVE {
return true
}

return false
}
53 changes: 53 additions & 0 deletions process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package web

import (
"fmt"
"os"
"path/filepath"
"strconv"
)

func pidFileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}

func readPidFile(path string) (int, error) {
data, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return 0, fmt.Errorf("error reading PID file: %w", err)
}

pid, err := strconv.Atoi(string(data))
if err != nil {
return 0, fmt.Errorf("error converting PID from file: %w", err)
}

return pid, nil
}

func killProcess(pid int) error {
process, err := os.FindProcess(pid)
if err != nil {
return fmt.Errorf("error finding process: %w", err)
}

if err = process.Kill(); err != nil {
return fmt.Errorf("error killing process: %w", err)
}

return nil
}

func interruptProcess(pid int) error {
process, err := os.FindProcess(pid)
if err != nil {
return fmt.Errorf("error finding process: %w", err)
}

if err = process.Signal(os.Interrupt); err != nil {
return fmt.Errorf("error interrupting process: %w", err)
}

return nil
}
6 changes: 3 additions & 3 deletions scripts/prebuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ func main() {
}

release := os.Args[1]
folderPath := os.Args[2]
dirPath := os.Args[2]

archivePath := filepath.Clean(filepath.Join(folderPath, "dist.tar.gz"))
resultPath := filepath.Clean(filepath.Join(folderPath, "."))
archivePath := filepath.Clean(filepath.Join(dirPath, "dist.tar.gz"))
resultPath := filepath.Clean(filepath.Join(dirPath, "."))

fmt.Println("Trying to download dist archive...")

Expand Down
4 changes: 0 additions & 4 deletions server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ func (l *launchrServer) GetCustomisationConfig(w http.ResponseWriter, _ *http.Re
var launchrConfig *launchrWebConfig
err := l.cfg.Get("web", &launchrConfig)
if err != nil {
log.Debug(err.Error())
sendError(w, http.StatusInternalServerError, "error getting config")
return
}
Expand All @@ -72,7 +71,6 @@ func (l *launchrServer) GetCustomisationConfig(w http.ResponseWriter, _ *http.Re

gvFile, err := parseVarsFile(launchrConfig.VarsFile)
if err != nil {
log.Debug(err.Error())
sendError(w, http.StatusInternalServerError, "error getting group vars file")
return
}
Expand Down Expand Up @@ -117,7 +115,6 @@ func (l *launchrServer) GetRunningActionStreams(w http.ResponseWriter, _ *http.R
}
sd, err := fStreams.GetStreamData(params)
if err != nil {
log.Debug(err.Error())
sendError(w, http.StatusInternalServerError, "Error reading streams")
}

Expand Down Expand Up @@ -231,7 +228,6 @@ func (l *launchrServer) RunAction(w http.ResponseWriter, r *http.Request, id str
// Can we fetch directly json?
streams, err := createFileStreams(runID)
if err != nil {
log.Debug(err.Error())
sendError(w, http.StatusInternalServerError, "Error preparing streams")
}

Expand Down
Loading

0 comments on commit 79486b9

Please sign in to comment.