github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/ui.go (about) 1 // Copyright 2015 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 // Package ui embeds the assets for the web UI into the Cockroach binary. 12 // 13 // By default, it serves a stub web UI. Linking with distoss or distccl will 14 // replace the stubs with the OSS UI or the CCL UI, respectively. The exported 15 // symbols in this package are thus function pointers instead of functions so 16 // that they can be mutated by init hooks. 17 package ui 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "html/template" 24 "net/http" 25 "os" 26 27 "github.com/cockroachdb/cockroach/pkg/base" 28 "github.com/cockroachdb/cockroach/pkg/build" 29 "github.com/cockroachdb/cockroach/pkg/util/log" 30 "github.com/cockroachdb/errors" 31 assetfs "github.com/elazarl/go-bindata-assetfs" 32 ) 33 34 // Asset loads and returns the asset for the given name. It returns an error if 35 // the asset could not be found or could not be loaded. 36 var Asset func(name string) ([]byte, error) 37 38 // AssetDir returns the file names below a certain directory in the embedded 39 // filesystem. 40 // 41 // For example, if the embedded filesystem contains the following hierarchy: 42 // 43 // data/ 44 // foo.txt 45 // img/ 46 // a.png 47 // b.png 48 // 49 // AssetDir("") returns []string{"data"} 50 // AssetDir("data") returns []string{"foo.txt", "img"} 51 // AssetDir("data/img") returns []string{"a.png", "b.png"} 52 // AssetDir("foo.txt") and AssetDir("notexist") return errors 53 var AssetDir func(name string) ([]string, error) 54 55 // AssetInfo loads and returns metadata for the asset with the given name. It 56 // returns an error if the asset could not be found or could not be loaded. 57 var AssetInfo func(name string) (os.FileInfo, error) 58 59 // haveUI returns whether the admin UI has been linked into the binary. 60 func haveUI() bool { 61 return Asset != nil && AssetDir != nil && AssetInfo != nil 62 } 63 64 // indexTemplate takes arguments about the current session and returns HTML 65 // which includes the UI JavaScript bundles, plus a script tag which sets the 66 // currently logged in user so that the UI JavaScript can decide whether to show 67 // a login page. 68 var indexHTMLTemplate = template.Must(template.New("index").Parse(`<!DOCTYPE html> 69 <html> 70 <head> 71 <title>Cockroach Console</title> 72 <meta charset="UTF-8"> 73 <link href="favicon.ico" rel="shortcut icon"> 74 </head> 75 <body> 76 <div id="react-layout"></div> 77 78 <script> 79 window.dataFromServer = {{.}}; 80 </script> 81 82 <script src="protos.dll.js" type="text/javascript"></script> 83 <script src="vendor.dll.js" type="text/javascript"></script> 84 <script src="bundle.js" type="text/javascript"></script> 85 </body> 86 </html> 87 `)) 88 89 type indexHTMLArgs struct { 90 ExperimentalUseLogin bool 91 LoginEnabled bool 92 LoggedInUser *string 93 Tag string 94 Version string 95 NodeID string 96 } 97 98 // bareIndexHTML is used in place of indexHTMLTemplate when the binary is built 99 // without the web UI. 100 var bareIndexHTML = []byte(fmt.Sprintf(`<!DOCTYPE html> 101 <title>CockroachDB</title> 102 Binary built without web UI. 103 <hr> 104 <em>%s</em>`, build.GetInfo().Short())) 105 106 // Config contains the configuration parameters for Handler. 107 type Config struct { 108 ExperimentalUseLogin bool 109 LoginEnabled bool 110 NodeID *base.NodeIDContainer 111 GetUser func(ctx context.Context) *string 112 } 113 114 // Handler returns an http.Handler that serves the UI, 115 // including index.html, which has some login-related variables 116 // templated into it, as well as static assets. 117 func Handler(cfg Config) http.Handler { 118 fileServer := http.FileServer(&assetfs.AssetFS{ 119 Asset: Asset, 120 AssetDir: AssetDir, 121 AssetInfo: AssetInfo, 122 }) 123 buildInfo := build.GetInfo() 124 125 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 126 if !haveUI() { 127 http.ServeContent(w, r, "index.html", buildInfo.GoTime(), bytes.NewReader(bareIndexHTML)) 128 return 129 } 130 131 if r.URL.Path != "/" { 132 fileServer.ServeHTTP(w, r) 133 return 134 } 135 136 if err := indexHTMLTemplate.Execute(w, indexHTMLArgs{ 137 ExperimentalUseLogin: cfg.ExperimentalUseLogin, 138 LoginEnabled: cfg.LoginEnabled, 139 LoggedInUser: cfg.GetUser(r.Context()), 140 Tag: buildInfo.Tag, 141 Version: build.VersionPrefix(), 142 NodeID: cfg.NodeID.String(), 143 }); err != nil { 144 err = errors.Wrap(err, "templating index.html") 145 http.Error(w, err.Error(), 500) 146 log.Errorf(r.Context(), "%v", err) 147 } 148 }) 149 }