diff --git a/Makefile b/Makefile index bd605f1d3..6b7ccfaf5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build check-clean clean cover cover-viz default docker docker-build docker-test docker-tag-push generate generate-protoc generate-pql gometalinter install install-build-deps install-golangci-lint install-gometalinter install-protoc install-protoc-gen-gofast install-peg prerelease prerelease-upload release release-build test testv testv-race testvsub testvsub-race +.PHONY: build check-clean clean build-lattice cover cover-viz default docker docker-build docker-test docker-tag-push generate generate-protoc generate-pql generate-statik gometalinter install install-build-deps install-golangci-lint install-gometalinter install-protoc install-protoc-gen-gofast install-peg install-statik prerelease prerelease-upload release release-build require-statik test testv testv-race testvsub testvsub-race CLONE_URL=github.com/pilosa/pilosa VERSION := $(shell git describe --tags 2> /dev/null || echo unknown) @@ -135,10 +135,18 @@ prerelease-upload: install: go install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa +build-lattice: require-yarn + git clone git@github.com:molecula/lattice.git + cd lattice && yarn install && yarn build + # `go generate` protocol buffers generate-protoc: require-protoc require-protoc-gen-gofast go generate github.com/pilosa/pilosa/v2/internal +# `go generate` statik assets (lattice UI) +generate-statik: build-lattice require-statik + go generate github.com/pilosa/pilosa/v2/statik + # `go generate` stringers generate-stringer: go generate github.com/pilosa/pilosa/v2 @@ -151,7 +159,7 @@ generate-proto-grpc: require-protoc require-protoc-gen-gofast protoc -I proto proto/pilosa.proto --go_out=plugins=grpc:proto # `go generate` all needed packages -generate: generate-protoc generate-stringer generate-pql +generate: generate-protoc generate-statik generate-stringer generate-pql # Create Docker image from Dockerfile docker: vendor @@ -347,7 +355,10 @@ require-%: $(info Verified build dependency "$*" is installed.),\ $(error Build dependency "$*" not installed. To install, try `make install-$*`)) -install-build-deps: install-protoc-gen-gofast install-protoc install-stringer install-peg +install-build-deps: install-protoc-gen-gofast install-protoc install-statik install-stringer install-peg + +install-statik: + go get -u github.com/rakyll/statik install-stringer: GO111MODULE=off go get -u golang.org/x/tools/cmd/stringer diff --git a/go.mod b/go.mod index b8d683b60..a5e8fa41a 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/prometheus/client_golang v1.0.0 github.com/prometheus/client_model v0.1.0 github.com/prometheus/prom2json v1.3.0 + github.com/rakyll/statik v0.1.7 github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect github.com/satori/go.uuid v1.2.0 github.com/shirou/gopsutil v2.18.12+incompatible diff --git a/go.sum b/go.sum index 5c5045296..47fd55364 100644 --- a/go.sum +++ b/go.sum @@ -167,6 +167,8 @@ github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNG github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/prom2json v1.3.0 h1:BlqrtbT9lLH3ZsOVhXPsHzFrApCTKRifB7gjJuypu6Y= github.com/prometheus/prom2json v1.3.0/go.mod h1:rMN7m0ApCowcoDlypBHlkNbp5eJQf/+1isKykIP5ZnM= +github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= +github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 h1:HQagqIiBmr8YXawX/le3+O26N+vPPC1PtjaF3mwnook= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= diff --git a/http/handler.go b/http/handler.go index ea14e097c..1aff0af04 100644 --- a/http/handler.go +++ b/http/handler.go @@ -56,6 +56,8 @@ import ( type Handler struct { Handler http.Handler + FileSystem pilosa.FileSystem + logger logger.Logger // Keeps the query argument validators for each handler @@ -107,6 +109,13 @@ func OptHandlerAPI(api *pilosa.API) handlerOption { } } +func OptHandlerFileSystem(fs pilosa.FileSystem) handlerOption { + return func(h *Handler) error { + h.FileSystem = fs + return nil + } +} + func OptHandlerLogger(logger logger.Logger) handlerOption { return func(h *Handler) error { h.logger = logger @@ -143,6 +152,7 @@ func NewHandler(opts ...handlerOption) (*Handler, error) { } }) handler := &Handler{ + FileSystem: pilosa.NopFileSystem, logger: logger.NopLogger, closeTimeout: time.Second * 30, } @@ -338,7 +348,9 @@ func (h *Handler) collectStats(next http.Handler) http.Handler { // newRouter creates a new mux http router. func newRouter(handler *Handler) *mux.Router { router := mux.NewRouter() - router.HandleFunc("/", handler.handleHome).Methods("GET").Name("Home") + router.HandleFunc("/", handler.handleLattice).Methods("GET") + router.HandleFunc("/{file}", handler.handleLattice).Methods("GET") + router.HandleFunc("/static/{file}", handler.handleLattice).Methods("GET") router.HandleFunc("/cluster/resize/abort", handler.handlePostClusterResizeAbort).Methods("POST").Name("PostClusterResizeAbort") router.HandleFunc("/cluster/resize/remove-node", handler.handlePostClusterResizeRemoveNode).Methods("POST").Name("PostClusterResizeRemoveNode") router.HandleFunc("/cluster/resize/set-coordinator", handler.handlePostClusterResizeSetCoordinator).Methods("POST").Name("PostClusterResizeSetCoordinator") @@ -422,6 +434,22 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.Handler.ServeHTTP(w, r) } +func (h *Handler) handleLattice(w http.ResponseWriter, r *http.Request) { + // If user is using curl, don't chuck HTML at them + if strings.HasPrefix(r.UserAgent(), "curl") { + http.Error(w, "Welcome. Pilosa is running. Visit https://www.pilosa.com/docs/ for more information or try the Lattice UI by visiting this URL in your browser.", http.StatusNotFound) + return + } + filesystem, err := h.FileSystem.New() + + if err != nil { + _ = h.writeQueryResponse(w, r, &pilosa.QueryResponse{Err: err}) + h.logger.Printf("Lattice UI is not available. Please run `make generate-statik` before building Pilosa with `make install`.") + return + } + http.FileServer(filesystem).ServeHTTP(w, r) +} + // successResponse is a general success/error struct for http responses. type successResponse struct { h *Handler @@ -490,10 +518,6 @@ func (r *successResponse) write(w http.ResponseWriter, err error) { } } -func (h *Handler) handleHome(w http.ResponseWriter, _ *http.Request) { - http.Error(w, "Welcome. Pilosa is running. Visit https://www.pilosa.com/docs/ for more information.", http.StatusNotFound) -} - // validHeaderAcceptJSON returns false if one or more Accept // headers are present, but none of them are "application/json" // (or any matching wildcard). Otherwise returns true. diff --git a/server/server.go b/server/server.go index 546db5d41..bfd54cead 100644 --- a/server/server.go +++ b/server/server.go @@ -46,6 +46,7 @@ import ( "github.com/pilosa/pilosa/v2/http" "github.com/pilosa/pilosa/v2/logger" "github.com/pilosa/pilosa/v2/prometheus" + "github.com/pilosa/pilosa/v2/statik" "github.com/pilosa/pilosa/v2/stats" "github.com/pilosa/pilosa/v2/statsd" "github.com/pilosa/pilosa/v2/syswrap" @@ -427,6 +428,7 @@ func (m *Command) SetupServer() error { http.OptHandlerAllowedOrigins(m.Config.Handler.AllowedOrigins), http.OptHandlerAPI(m.API), http.OptHandlerLogger(m.logger), + http.OptHandlerFileSystem(&statik.FileSystem{}), http.OptHandlerListener(m.ln), http.OptHandlerCloseTimeout(m.closeTimeout), ) diff --git a/statik/.gitignore b/statik/.gitignore new file mode 100644 index 000000000..485c0c57d --- /dev/null +++ b/statik/.gitignore @@ -0,0 +1 @@ +/statik.go diff --git a/statik/filesystem.go b/statik/filesystem.go new file mode 100644 index 000000000..a5ec437b5 --- /dev/null +++ b/statik/filesystem.go @@ -0,0 +1,37 @@ +// Copyright 2017 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//go:generate statik -src=../lattice/build -dest=../ +// +// Package statik contains static assets for the Web UI. `go generate` or +// `make generate-statik` will produce statik.go, which is ignored by git. +package statik + +import ( + "net/http" + + "github.com/pilosa/pilosa/v2" + "github.com/rakyll/statik/fs" +) + +// Ensure nopFileSystem implements interface. +var _ pilosa.FileSystem = &FileSystem{} + +// FileSystem represents a static FileSystem. +type FileSystem struct{} + +// New is a statik implementation of FileSystem New method. +func (s *FileSystem) New() (http.FileSystem, error) { + return fs.New() +}