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}