webui.go

  1package commands
  2
  3import (
  4	"context"
  5	"fmt"
  6	"log"
  7	"net/http"
  8	"os"
  9	"os/signal"
 10	"time"
 11
 12	"github.com/99designs/gqlgen/graphql/playground"
 13	"github.com/gorilla/mux"
 14	"github.com/phayes/freeport"
 15	"github.com/skratchdot/open-golang/open"
 16	"github.com/spf13/cobra"
 17
 18	"github.com/MichaelMure/git-bug/api/auth"
 19	"github.com/MichaelMure/git-bug/api/graphql"
 20	httpapi "github.com/MichaelMure/git-bug/api/http"
 21	"github.com/MichaelMure/git-bug/cache"
 22	"github.com/MichaelMure/git-bug/identity"
 23	"github.com/MichaelMure/git-bug/repository"
 24	"github.com/MichaelMure/git-bug/webui"
 25)
 26
 27var (
 28	webUIPort     int
 29	webUIOpen     bool
 30	webUINoOpen   bool
 31	webUIReadOnly bool
 32)
 33
 34const webUIOpenConfigKey = "git-bug.webui.open"
 35
 36func runWebUI(cmd *cobra.Command, args []string) error {
 37	if webUIPort == 0 {
 38		var err error
 39		webUIPort, err = freeport.GetFreePort()
 40		if err != nil {
 41			return err
 42		}
 43	}
 44
 45	addr := fmt.Sprintf("127.0.0.1:%d", webUIPort)
 46	webUiAddr := fmt.Sprintf("http://%s", addr)
 47
 48	router := mux.NewRouter()
 49
 50	// If the webUI is not read-only, use an authentication middleware with a
 51	// fixed identity: the default user of the repo
 52	// TODO: support dynamic authentication with OAuth
 53	if !webUIReadOnly {
 54		author, err := identity.GetUserIdentity(repo)
 55		if err != nil {
 56			return err
 57		}
 58		router.Use(auth.Middleware(author.Id()))
 59	}
 60
 61	mrc := cache.NewMultiRepoCache()
 62	_, err := mrc.RegisterDefaultRepository(repo)
 63	if err != nil {
 64		return err
 65	}
 66
 67	graphqlHandler := graphql.NewHandler(mrc)
 68
 69	// Routes
 70	router.Path("/playground").Handler(playground.Handler("git-bug", "/graphql"))
 71	router.Path("/graphql").Handler(graphqlHandler)
 72	router.Path("/gitfile/{repo}/{hash}").Handler(httpapi.NewGitFileHandler(mrc))
 73	router.Path("/upload/{repo}").Methods("POST").Handler(httpapi.NewGitUploadFileHandler(mrc))
 74	router.PathPrefix("/").Handler(webui.NewHandler())
 75
 76	srv := &http.Server{
 77		Addr:    addr,
 78		Handler: router,
 79	}
 80
 81	done := make(chan bool)
 82	quit := make(chan os.Signal, 1)
 83
 84	// register as handler of the interrupt signal to trigger the teardown
 85	signal.Notify(quit, os.Interrupt)
 86
 87	go func() {
 88		<-quit
 89		fmt.Println("WebUI is shutting down...")
 90
 91		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 92		defer cancel()
 93
 94		srv.SetKeepAlivesEnabled(false)
 95		if err := srv.Shutdown(ctx); err != nil {
 96			log.Fatalf("Could not gracefully shutdown the WebUI: %v\n", err)
 97		}
 98
 99		// Teardown
100		err := graphqlHandler.Close()
101		if err != nil {
102			fmt.Println(err)
103		}
104
105		close(done)
106	}()
107
108	fmt.Printf("Web UI: %s\n", webUiAddr)
109	fmt.Printf("Graphql API: http://%s/graphql\n", addr)
110	fmt.Printf("Graphql Playground: http://%s/playground\n", addr)
111	fmt.Println("Press Ctrl+c to quit")
112
113	configOpen, err := repo.LocalConfig().ReadBool(webUIOpenConfigKey)
114	if err == repository.ErrNoConfigEntry {
115		// default to true
116		configOpen = true
117	} else if err != nil {
118		return err
119	}
120
121	shouldOpen := (configOpen && !webUINoOpen) || webUIOpen
122
123	if shouldOpen {
124		err = open.Run(webUiAddr)
125		if err != nil {
126			fmt.Println(err)
127		}
128	}
129
130	err = srv.ListenAndServe()
131	if err != nil && err != http.ErrServerClosed {
132		return err
133	}
134
135	<-done
136
137	fmt.Println("WebUI stopped")
138	return nil
139}
140
141var webUICmd = &cobra.Command{
142	Use:   "webui",
143	Short: "Launch the web UI.",
144	Long: `Launch the web UI.
145
146Available git config:
147  git-bug.webui.open [bool]: control the automatic opening of the web UI in the default browser
148`,
149	PreRunE: loadRepo,
150	RunE:    runWebUI,
151}
152
153func init() {
154	RootCmd.AddCommand(webUICmd)
155
156	webUICmd.Flags().SortFlags = false
157
158	webUICmd.Flags().BoolVar(&webUIOpen, "open", false, "Automatically open the web UI in the default browser")
159	webUICmd.Flags().BoolVar(&webUINoOpen, "no-open", false, "Prevent the automatic opening of the web UI in the default browser")
160	webUICmd.Flags().IntVarP(&webUIPort, "port", "p", 0, "Port to listen to (default is random)")
161	webUICmd.Flags().BoolVar(&webUIReadOnly, "read-only", false, "Whether to run the web UI in read-only mode")
162}