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  }