ui: modify content path (#5950)

* Add ui-content-path flag

* tests complete, regex validator on string, index.html updated

* cleaning up debugging stuff

* ui: Enable ember environment configuration to be set via the go binary at runtime (#5934)

* ui: Only inject {{.ContentPath}} if we are makeing a prod build...

...otherwise we just use the current rootURL

This gets injected into a <base /> node which solves the assets path
problem but not the ember problem

* ui: Pull out the <base href=""> value and inject it into ember env

See previous commit:

The <base href=""> value is 'sometimes' injected from go at index
serve time. We pass this value down to ember by overwriting the ember
config that is injected via a <meta> tag. This has to be done before
ember bootup.

Sometimes (during testing and development, basically not production)
this is injected with the already existing value, in which case this
essentially changes nothing.

The code here is slightly abstracted away from our specific usage to
make it easier for anyone else to use, and also make sure we can cope
with using this same method to pass variables down from the CLI through
to ember in the future.

* ui: We can't use <base /> move everything to javascript (#5941)

Unfortuantely we can't seem to be able to use <base> and rootURL
together as URL paths will get doubled up (`ui/ui/`).

This moves all the things that we need to interpolate with .ContentPath
to the `startup` javascript so we can conditionally print out
`{{.ContentPath}}` in lots of places (now we can't use base)

* fixed when we serve index.html

* ui: For writing a ContentPath, we also need to cope with testing... (#5945)

...and potentially more environments

Testing has more additional things in a separate index.html in `tests/`

This make the entire thing a little saner and uses just javascriopt
template literals instead of a pseudo handbrake synatx for our
templating of these files.

Intead of just templating the entire file this way, we still only
template `{{content-for 'head'}}` and `{{content-for 'body'}}`
in this way to ensure we support other plugins/addons

* build: Loosen up the regex for retrieving the CONSUL_VERSION (#5946)

* build: Loosen up the regex for retrieving the CONSUL_VERSION

1. Previously the `sed` replacement was searching for the CONSUL_VERSION
comment at the start of a line, it no longer does this to allow for
indentation.
2. Both `grep` and `sed` where looking for the omment at the end of the
line. We've removed this restriction here. We don't need to remove it
right now, but if we ever put the comment followed by something here the
searching would break.
3. Added `xargs` for trimming the resulting version string. We aren't
using this already in the rest of the scripts, but we are pretty sure
this is available on most systems.

* ui: Fix erroneous variable, and also force an ember cache clean on build

1. We referenced a variable incorrectly here, this fixes that.
2. We also made sure that every `make` target clears ember's `tmp` cache
to ensure that its not using any caches that have since been edited
everytime we call a `make` target.

* added docs, fixed encoding

* fixed go fmt

* Update agent/config/config.go

Co-Authored-By: R.B. Boyer <public@richardboyer.net>

* Completed Suggestions

* run gofmt on http.go

* fix testsanitize

* fix fullconfig/hcl by setting correct 'want'

* ran gofmt on agent/config/runtime_test.go

* Update website/source/docs/agent/options.html.md

Co-Authored-By: Hans Hasselberg <me@hans.io>

* Update website/source/docs/agent/options.html.md

Co-Authored-By: kaitlincarter-hc <43049322+kaitlincarter-hc@users.noreply.github.com>

* remove contentpath from redirectFS struct
This commit is contained in:
Sarah Christoff 2019-06-26 11:43:30 -05:00 committed by GitHub
parent 0a3e3aa882
commit d3d92d76f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 311 additions and 106 deletions

File diff suppressed because one or more lines are too long

View File

@ -882,6 +882,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
TaggedAddresses: c.TaggedAddresses, TaggedAddresses: c.TaggedAddresses,
TranslateWANAddrs: b.boolVal(c.TranslateWANAddrs), TranslateWANAddrs: b.boolVal(c.TranslateWANAddrs),
UIDir: b.stringVal(c.UIDir), UIDir: b.stringVal(c.UIDir),
UIContentPath: UIPathBuilder(b.stringVal(b.Flags.Config.UIContentPath)),
UnixSocketGroup: b.stringVal(c.UnixSocket.Group), UnixSocketGroup: b.stringVal(c.UnixSocket.Group),
UnixSocketMode: b.stringVal(c.UnixSocket.Mode), UnixSocketMode: b.stringVal(c.UnixSocket.Mode),
UnixSocketUser: b.stringVal(c.UnixSocket.User), UnixSocketUser: b.stringVal(c.UnixSocket.User),
@ -907,6 +908,9 @@ func (b *Builder) Validate(rt RuntimeConfig) error {
// reDatacenter defines a regexp for a valid datacenter name // reDatacenter defines a regexp for a valid datacenter name
var reDatacenter = regexp.MustCompile("^[a-z0-9_-]+$") var reDatacenter = regexp.MustCompile("^[a-z0-9_-]+$")
// validContentPath defines a regexp for a valid content path name.
var validContentPath = regexp.MustCompile(`^[A-Za-z0-9/_-]+$`)
var hasVersion = regexp.MustCompile(`^/v\d+/$`)
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// check required params we cannot recover from first // check required params we cannot recover from first
// //
@ -915,11 +919,20 @@ func (b *Builder) Validate(rt RuntimeConfig) error {
return fmt.Errorf("datacenter cannot be empty") return fmt.Errorf("datacenter cannot be empty")
} }
if !reDatacenter.MatchString(rt.Datacenter) { if !reDatacenter.MatchString(rt.Datacenter) {
return fmt.Errorf("datacenter cannot be %q. Please use only [a-z0-9-_].", rt.Datacenter) return fmt.Errorf("datacenter cannot be %q. Please use only [a-z0-9-_]", rt.Datacenter)
} }
if rt.DataDir == "" && !rt.DevMode { if rt.DataDir == "" && !rt.DevMode {
return fmt.Errorf("data_dir cannot be empty") return fmt.Errorf("data_dir cannot be empty")
} }
if !validContentPath.MatchString(rt.UIContentPath) {
return fmt.Errorf("ui-content-path can only contain alphanumeric, -, _, or /. received: %s", rt.UIContentPath)
}
if hasVersion.MatchString(rt.UIContentPath) {
return fmt.Errorf("ui-content-path cannot have 'v[0-9]'. received: %s", rt.UIContentPath)
}
if !rt.DevMode { if !rt.DevMode {
fi, err := os.Stat(rt.DataDir) fi, err := os.Stat(rt.DataDir)
switch { switch {
@ -976,7 +989,7 @@ func (b *Builder) Validate(rt RuntimeConfig) error {
return fmt.Errorf("autopilot.max_trailing_logs cannot be %d. Must be greater than or equal to zero", rt.AutopilotMaxTrailingLogs) return fmt.Errorf("autopilot.max_trailing_logs cannot be %d. Must be greater than or equal to zero", rt.AutopilotMaxTrailingLogs)
} }
if rt.ACLDatacenter != "" && !reDatacenter.MatchString(rt.ACLDatacenter) { if rt.ACLDatacenter != "" && !reDatacenter.MatchString(rt.ACLDatacenter) {
return fmt.Errorf("acl_datacenter cannot be %q. Please use only [a-z0-9-_].", rt.ACLDatacenter) return fmt.Errorf("acl_datacenter cannot be %q. Please use only [a-z0-9-_]", rt.ACLDatacenter)
} }
if rt.EnableUI && rt.UIDir != "" { if rt.EnableUI && rt.UIDir != "" {
return fmt.Errorf( return fmt.Errorf(
@ -1672,3 +1685,17 @@ func isUnixAddr(a net.Addr) bool {
_, ok := a.(*net.UnixAddr) _, ok := a.(*net.UnixAddr)
return ok return ok
} }
// UIPathBuilder checks to see if there was a path set
// If so, adds beginning and trailing slashes to UI path
func UIPathBuilder(UIContentString string) string {
if UIContentString != "" {
var fmtedPath string
fmtedPath = strings.Trim(UIContentString, "/")
fmtedPath = "/" + fmtedPath + "/"
return fmtedPath
}
return "/ui/"
}

View File

@ -265,6 +265,7 @@ type Config struct {
Telemetry Telemetry `json:"telemetry,omitempty" hcl:"telemetry" mapstructure:"telemetry"` Telemetry Telemetry `json:"telemetry,omitempty" hcl:"telemetry" mapstructure:"telemetry"`
TranslateWANAddrs *bool `json:"translate_wan_addrs,omitempty" hcl:"translate_wan_addrs" mapstructure:"translate_wan_addrs"` TranslateWANAddrs *bool `json:"translate_wan_addrs,omitempty" hcl:"translate_wan_addrs" mapstructure:"translate_wan_addrs"`
UI *bool `json:"ui,omitempty" hcl:"ui" mapstructure:"ui"` UI *bool `json:"ui,omitempty" hcl:"ui" mapstructure:"ui"`
UIContentPath *string `json:"ui_content_path,omitempty" hcl:"ui_content_path" mapstructure:"ui_content_path"`
UIDir *string `json:"ui_dir,omitempty" hcl:"ui_dir" mapstructure:"ui_dir"` UIDir *string `json:"ui_dir,omitempty" hcl:"ui_dir" mapstructure:"ui_dir"`
UnixSocket UnixSocket `json:"unix_sockets,omitempty" hcl:"unix_sockets" mapstructure:"unix_sockets"` UnixSocket UnixSocket `json:"unix_sockets,omitempty" hcl:"unix_sockets" mapstructure:"unix_sockets"`
VerifyIncoming *bool `json:"verify_incoming,omitempty" hcl:"verify_incoming" mapstructure:"verify_incoming"` VerifyIncoming *bool `json:"verify_incoming,omitempty" hcl:"verify_incoming" mapstructure:"verify_incoming"`

View File

@ -106,6 +106,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) {
add(&f.Config.ServerMode, "server", "Switches agent to server mode.") add(&f.Config.ServerMode, "server", "Switches agent to server mode.")
add(&f.Config.EnableSyslog, "syslog", "Enables logging to syslog.") add(&f.Config.EnableSyslog, "syslog", "Enables logging to syslog.")
add(&f.Config.UI, "ui", "Enables the built-in static web UI server.") add(&f.Config.UI, "ui", "Enables the built-in static web UI server.")
add(&f.Config.UIContentPath, "ui-content-path", "Sets the external UI path to a string. Defaults to: /ui/ ")
add(&f.Config.UIDir, "ui-dir", "Path to directory containing the web UI resources.") add(&f.Config.UIDir, "ui-dir", "Path to directory containing the web UI resources.")
add(&f.HCL, "hcl", "hcl config fragment. Can be specified multiple times.") add(&f.HCL, "hcl", "hcl config fragment. Can be specified multiple times.")
} }

View File

@ -1387,6 +1387,10 @@ type RuntimeConfig struct {
// flag: -ui-dir string // flag: -ui-dir string
UIDir string UIDir string
//UIContentPath is a string that sets the external
// path to a string. Default: /ui/
UIContentPath string
// UnixSocketGroup contains the group of the file permissions when // UnixSocketGroup contains the group of the file permissions when
// Consul binds to UNIX sockets. // Consul binds to UNIX sockets.
// //

View File

@ -743,6 +743,18 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
rt.DataDir = dataDir rt.DataDir = dataDir
}, },
}, },
{
desc: "-ui-content-path",
args: []string{
`-ui-content-path=/a/b`,
`-data-dir=` + dataDir,
},
patch: func(rt *RuntimeConfig) {
rt.UIContentPath = "/a/b/"
rt.DataDir = dataDir
},
},
// ------------------------------------------------------------ // ------------------------------------------------------------
// ports and addresses // ports and addresses
@ -4760,6 +4772,7 @@ func TestFullConfig(t *testing.T) {
"wan": "78.63.37.19", "wan": "78.63.37.19",
}, },
TranslateWANAddrs: true, TranslateWANAddrs: true,
UIContentPath: "/ui/",
UIDir: "11IFzAUn", UIDir: "11IFzAUn",
UnixSocketUser: "E0nB1DwA", UnixSocketUser: "E0nB1DwA",
UnixSocketGroup: "8pFodrV8", UnixSocketGroup: "8pFodrV8",
@ -5369,6 +5382,7 @@ func TestSanitize(t *testing.T) {
"StatsiteAddr": "" "StatsiteAddr": ""
}, },
"TranslateWANAddrs": false, "TranslateWANAddrs": false,
"UIContentPath": "",
"UIDir": "", "UIDir": "",
"UnixSocketGroup": "", "UnixSocketGroup": "",
"UnixSocketMode": "", "UnixSocketMode": "",
@ -5709,6 +5723,41 @@ func TestReadPath(t *testing.T) {
} }
} }
func Test_UIPathBuilder(t *testing.T) {
cases := []struct {
name string
path string
expected string
}{
{
"Letters only string",
"hello",
"/hello/",
},
{
"Alphanumeric",
"Hello1",
"/Hello1/",
},
{
"Hyphen and underscore",
"-_",
"/-_/",
},
{
"Many slashes",
"/hello/ui/1/",
"/hello/ui/1/",
},
}
for _, tt := range cases {
actual := UIPathBuilder(tt.path)
require.Equal(t, tt.expected, actual)
}
}
func splitIPPort(hostport string) (net.IP, int) { func splitIPPort(hostport string) (net.IP, int) {
h, p, err := net.SplitHostPort(hostport) h, p, err := net.SplitHostPort(hostport)
if err != nil { if err != nil {

View File

@ -1,17 +1,21 @@
package agent package agent
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
"net/url" "net/url"
"os"
"reflect" "reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"text/template"
"time" "time"
"github.com/NYTimes/gziphandler" "github.com/NYTimes/gziphandler"
@ -83,6 +87,66 @@ type HTTPServer struct {
// proto is filled by the agent to "http" or "https". // proto is filled by the agent to "http" or "https".
proto string proto string
} }
type templatedFile struct {
templated *bytes.Reader
name string
mode os.FileMode
modTime time.Time
}
func newTemplatedFile(buf *bytes.Buffer, raw http.File) *templatedFile {
info, _ := raw.Stat()
return &templatedFile{
templated: bytes.NewReader(buf.Bytes()),
name: info.Name(),
mode: info.Mode(),
modTime: info.ModTime(),
}
}
func (t *templatedFile) Read(p []byte) (n int, err error) {
return t.templated.Read(p)
}
func (t *templatedFile) Seek(offset int64, whence int) (int64, error) {
return t.templated.Seek(offset, whence)
}
func (t *templatedFile) Close() error {
return nil
}
func (t *templatedFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, errors.New("not a directory")
}
func (t *templatedFile) Stat() (os.FileInfo, error) {
return t, nil
}
func (t *templatedFile) Name() string {
return t.name
}
func (t *templatedFile) Size() int64 {
return int64(t.templated.Len())
}
func (t *templatedFile) Mode() os.FileMode {
return t.mode
}
func (t *templatedFile) ModTime() time.Time {
return t.modTime
}
func (t *templatedFile) IsDir() bool {
return false
}
func (t *templatedFile) Sys() interface{} {
return nil
}
type redirectFS struct { type redirectFS struct {
fs http.FileSystem fs http.FileSystem
@ -96,6 +160,25 @@ func (fs *redirectFS) Open(name string) (http.File, error) {
return file, err return file, err
} }
type templatedIndexFS struct {
fs http.FileSystem
ContentPath string
}
func (fs *templatedIndexFS) Open(name string) (http.File, error) {
file, err := fs.fs.Open(name)
if err == nil && name == "/index.html" {
content, _ := ioutil.ReadAll(file)
file.Seek(0, 0)
t, _ := template.New("fmtedindex").Parse(string(content))
var out bytes.Buffer
err = t.Execute(&out, fs)
file = newTemplatedFile(&out, file)
}
return file, err
}
// endpoint is a Consul-specific HTTP handler that takes the usual arguments in // endpoint is a Consul-specific HTTP handler that takes the usual arguments in
// but returns a response object and error, both of which are handled in a // but returns a response object and error, both of which are handled in a
// common manner by Consul's HTTP server. // common manner by Consul's HTTP server.
@ -207,7 +290,6 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
handleFuncMetrics(pattern, http.HandlerFunc(wrapper)) handleFuncMetrics(pattern, http.HandlerFunc(wrapper))
} }
mux.HandleFunc("/", s.Index) mux.HandleFunc("/", s.Index)
for pattern, fn := range endpoints { for pattern, fn := range endpoints {
thisFn := fn thisFn := fn
@ -227,7 +309,6 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
if s.IsUIEnabled() { if s.IsUIEnabled() {
var uifs http.FileSystem var uifs http.FileSystem
// Use the custom UI dir if provided. // Use the custom UI dir if provided.
if s.agent.config.UIDir != "" { if s.agent.config.UIDir != "" {
uifs = http.Dir(s.agent.config.UIDir) uifs = http.Dir(s.agent.config.UIDir)
@ -235,10 +316,9 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
fs := assetFS() fs := assetFS()
uifs = fs uifs = fs
} }
uifs = &redirectFS{fs: uifs} uifs = &redirectFS{fs: &templatedIndexFS{fs: uifs, ContentPath: s.agent.config.UIContentPath}}
mux.Handle("/robots.txt", http.FileServer(uifs)) mux.Handle("/robots.txt", http.FileServer(uifs))
mux.Handle("/ui/", http.StripPrefix("/ui/", http.FileServer(uifs))) mux.Handle(s.agent.config.UIContentPath, http.StripPrefix(s.agent.config.UIContentPath, http.FileServer(uifs)))
} }
// Wrap the whole mux with a handler that bans URLs with non-printable // Wrap the whole mux with a handler that bans URLs with non-printable
@ -489,7 +569,7 @@ func (s *HTTPServer) Index(resp http.ResponseWriter, req *http.Request) {
} }
// Redirect to the UI endpoint // Redirect to the UI endpoint
http.Redirect(resp, req, "/ui/", http.StatusMovedPermanently) // 301 http.Redirect(resp, req, s.agent.config.UIContentPath, http.StatusMovedPermanently) // 301
} }
// decodeBody is used to decode a JSON request body // decodeBody is used to decode a JSON request body

View File

@ -974,7 +974,7 @@ function ui_version {
return 1 return 1
fi fi
local ui_version="$(grep '<!-- CONSUL_VERSION: .* -->$' "$1" | sed 's/^<!-- CONSUL_VERSION: \(.*\) -->$/\1/')" || return 1 local ui_version="$(grep '<!-- CONSUL_VERSION: .* -->' "$1" | sed 's/<!-- CONSUL_VERSION: \(.*\) -->/\1/' | xargs)" || return 1
echo "$ui_version" echo "$ui_version"
return 0 return 0
} }

View File

@ -2,7 +2,13 @@ ROOT:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
all: build all: build
deps: node_modules deps: node_modules clean
clean:
rm -rf ./tmp
build-ci: deps
yarn run build-ci --output-path=dist
build: deps build: deps
yarn run build yarn run build
@ -19,6 +25,9 @@ test: deps
test-view: deps test-view: deps
yarn run test:view yarn run test:view
test-parallel: deps
yarn test-parallel
lint: deps lint: deps
yarn run lint:js yarn run lint:js
@ -31,4 +40,4 @@ steps:
node_modules: yarn.lock package.json node_modules: yarn.lock package.json
yarn install yarn install
.PHONY: all deps build start test test-view lint format .PHONY: all deps build start test test-view lint format clean

View File

@ -6,51 +6,12 @@
<title>Consul by HashiCorp</title> <title>Consul by HashiCorp</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for "// all HTML content is populated via ./lib/startup/index"}}
{{content-for "head"}} {{content-for "head"}}
<link rel="icon" type="image/png" href="{{rootURL}}assets/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="{{rootURL}}assets/favicon-16x16.png" sizes="16x16">
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css">
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/consul-ui.css">
{{content-for "head-footer"}} {{content-for "head-footer"}}
</head> </head>
<body> <body>
<noscript>
<div style="margin: 0 auto;">
<h2>JavaScript Required</h2>
<p>Please enable JavaScript in your web browser to use Consul UI.</p>
</div>
</noscript>
{{content-for "body"}} {{content-for "body"}}
<script src="{{rootURL}}assets/vendor.js"></script>
<script>
var appendScript = function(src) {
var $script = document.createElement('script');
$script.src = src;
document.body.appendChild($script);
}
if(!('TextDecoder' in window)) {
appendScript('{{rootURL}}assets/encoding-indexes.js');
appendScript('{{rootURL}}assets/encoding.js');
}
</script>
<script src="{{rootURL}}assets/consul-ui.js"></script>
<script>
CodeMirror.modeURL = {
replace: function(n, mode) {
switch(mode) {
case 'javascript':
return '{{rootURL}}assets/codemirror/mode/javascript/javascript.js';
case 'ruby':
return '{{rootURL}}assets/codemirror/mode/ruby/ruby.js';
case 'yaml':
return '{{rootURL}}assets/codemirror/mode/yaml/yaml.js';
}
}
};
</script>
{{content-for "body-footer"}} {{content-for "body-footer"}}
</body> </body>
</html> </html>

View File

@ -3,16 +3,17 @@
module.exports = { module.exports = {
name: 'startup', name: 'startup',
contentFor: function(type, config) { contentFor: function(type, config) {
const enterprise = config.CONSUL_BINARY_TYPE !== 'oss' && config.CONSUL_BINARY_TYPE !== ''; const vars = {
appName: config.modulePrefix,
environment: config.environment,
rootURL: config.environment === 'production' ? '{{.ContentPath}}' : config.rootURL,
config: config,
};
switch (type) { switch (type) {
case 'head': case 'head':
return `<!-- CONSUL_VERSION: ${config.CONSUL_VERSION} -->`; return require('./templates/head.html.js')(vars);
case 'body': case 'body':
return `<svg width="168" height="53" xmlns="http://www.w3.org/2000/svg"><g fill="#919FA8" fill-rule="evenodd"><path d="M26.078 32.12a5.586 5.586 0 1 1 5.577-5.599 5.577 5.577 0 0 1-5.577 5.6M37.009 29.328a2.56 2.56 0 1 1 2.56-2.56 2.551 2.551 0 0 1-2.56 2.56M46.916 31.669a2.56 2.56 0 1 1 .051-.21c-.028.066-.028.13-.051.21M44.588 25.068a2.565 2.565 0 0 1-2.672-.992 2.558 2.558 0 0 1-.102-2.845 2.564 2.564 0 0 1 4.676.764c.072.328.081.667.027 1a2.463 2.463 0 0 1-1.925 2.073M53.932 31.402a2.547 2.547 0 0 1-2.95 2.076 2.559 2.559 0 0 1-2.064-2.965 2.547 2.547 0 0 1 2.948-2.077 2.57 2.57 0 0 1 2.128 2.716.664.664 0 0 0-.05.228M51.857 25.103a2.56 2.56 0 1 1 2.108-2.945c.034.218.043.439.027.658a2.547 2.547 0 0 1-2.135 2.287M49.954 40.113a2.56 2.56 0 1 1 .314-1.037c-.02.366-.128.721-.314 1.037M48.974 16.893a2.56 2.56 0 1 1 .97-3.487c.264.446.375.965.317 1.479a2.56 2.56 0 0 1-1.287 2.008"/><path d="M26.526 52.603c-14.393 0-26.06-11.567-26.06-25.836C.466 12.498 12.133.931 26.526.931a25.936 25.936 0 0 1 15.836 5.307l-3.167 4.117A20.962 20.962 0 0 0 17.304 8.23C10.194 11.713 5.7 18.9 5.714 26.763c-.014 7.862 4.48 15.05 11.59 18.534a20.962 20.962 0 0 0 21.89-2.127l3.168 4.123a25.981 25.981 0 0 1-15.836 5.31z"/>${ return require('./templates/body.html.js')(vars);
enterprise
? `<path data-enterprise-logo d="M61 42.083h3.975v.785H61.87v2.136h2.882v.784H61.87v2.31h3.114v.785H61v-6.8zm6.907 1.018V48.9h-.828v-6.817h1.2l2.94 5.84v-5.84h.829V48.9h-1.193L67.907 43.1zm7.826-.225h-2.012v-.784h4.911v.784h-2.02V48.9h-.879v-6.024zm4.564-.793h3.975v.785h-3.106v2.136h2.882v.784h-2.882v2.31h3.114v.785h-3.992l.009-6.8zm8.605 4.347h-1.657v2.503h-.87v-6.85h2.576c1.45 0 1.963.635 1.963 1.67v.984a1.435 1.435 0 0 1-1.077 1.585l1.756 2.57h-1.002l-1.69-2.462zm0-3.562h-1.657v2.778h1.657c.828 0 1.118-.234 1.118-.901v-.968c.024-.676-.265-.901-1.094-.901l-.024-.008zm4.488-.785h2.485c1.45 0 1.963.635 1.963 1.67v1.009c0 1.05-.505 1.668-1.963 1.668H94.3v2.47h-.87l-.04-6.817zm2.419.785h-1.54v2.803h1.54c.828 0 1.118-.234 1.118-.901v-1.001c0-.668-.282-.893-1.118-.893v-.008zm6.368 3.562h-1.656v2.503h-.87v-6.85h2.576c1.45 0 1.963.635 1.963 1.67v.984a1.435 1.435 0 0 1-1.077 1.585l1.756 2.57h-1.002l-1.69-2.462zm0-3.562h-1.656v2.778h1.656c.829 0 1.118-.234 1.118-.901v-.968c.017-.676-.265-.901-1.101-.901l-.017-.008zm5.392 6.032h-.828v-6.817h.828V48.9zm4.14.1a5.76 5.76 0 0 1-2.012-.359l.141-.717c.605.195 1.236.3 1.872.308 1.085 0 1.308-.283 1.308-1.06 0-.917 0-1-1.4-1.317-1.656-.368-1.83-.685-1.83-2.095 0-1.184.49-1.76 2.162-1.76a7.648 7.648 0 0 1 1.83.225l-.074.743a8.223 8.223 0 0 0-1.74-.192c-1.11 0-1.308.225-1.308 1.01 0 .942 0 .984 1.342 1.318 1.797.45 1.888.717 1.888 2.044.033 1.176-.315 1.852-2.178 1.852zm4.332-6.917h3.95v.785h-3.105v2.136h2.882v.784h-2.882v2.31H120v.785h-3.992l.033-6.8z" fill-rule="nonzero"/>`
: ''
}<path d="M61 30.15V17.948c0-4.962 2.845-7.85 9.495-7.85 2.484 0 5.048.326 7.252.895l-.561 4.433c-2.164-.406-4.688-.691-6.53-.691-3.486 0-4.608 1.22-4.608 4.108v10.412c0 2.888 1.122 4.108 4.607 4.108 1.843 0 4.367-.284 6.53-.691l.562 4.433c-2.204.57-4.768.895-7.252.895C63.845 38 61 35.112 61 30.15zm36.808.04c0 4.068-1.802 7.81-8.493 7.81-6.69 0-8.494-3.742-8.494-7.81v-5.002c0-4.067 1.803-7.81 8.494-7.81 6.69 0 8.493 3.743 8.493 7.81v5.003zm-4.887-5.165c0-2.237-1.002-3.416-3.606-3.416s-3.606 1.18-3.606 3.416v5.328c0 2.237 1.002 3.417 3.606 3.417s3.606-1.18 3.606-3.417v-5.328zm25.79 12.568h-4.887V23.764c0-1.057-.44-1.586-1.563-1.586-1.201 0-3.325.732-5.088 1.668v13.747h-4.887V17.785h3.726l.48 1.668c2.444-1.22 5.53-2.074 7.813-2.074 3.245 0 4.407 2.318 4.407 5.857v14.357zm18.26-5.775c0 3.823-1.162 6.182-7.052 6.182-2.083 0-4.927-.488-6.73-1.139l.68-3.782c1.643.488 3.807.854 5.81.854 2.164 0 2.484-.488 2.484-1.993 0-1.22-.24-1.83-3.405-2.603-4.768-1.18-5.329-2.4-5.329-6.223 0-3.986 1.723-5.735 7.292-5.735 1.803 0 4.166.244 5.85.691l-.482 3.945c-1.482-.284-3.846-.569-5.368-.569-2.124 0-2.484.488-2.484 1.708 0 1.587.12 1.709 2.764 2.4 5.449 1.464 5.97 2.196 5.97 6.264zm4.357-14.033h4.887v13.83c0 1.057.441 1.586 1.563 1.586 1.202 0 3.325-.733 5.088-1.668V17.785h4.888v19.808h-3.726l-.481-1.667c-2.444 1.22-5.529 2.074-7.812 2.074-3.246 0-4.407-2.318-4.407-5.857V17.785zM168 37.593h-4.888V9.691L168 9v28.593z"/></g></svg>`;
case 'root-class': case 'root-class':
return 'ember-loading'; return 'ember-loading';
} }

View File

@ -0,0 +1,42 @@
module.exports = ({ appName, environment, rootURL, config }) => `
<noscript>
<div style="margin: 0 auto;">
<h2>JavaScript Required</h2>
<p>Please enable JavaScript in your web browser to use Consul UI.</p>
</div>
</noscript>
<svg width="168" height="53" xmlns="http://www.w3.org/2000/svg"><g fill="#919FA8" fill-rule="evenodd"><path d="M26.078 32.12a5.586 5.586 0 1 1 5.577-5.599 5.577 5.577 0 0 1-5.577 5.6M37.009 29.328a2.56 2.56 0 1 1 2.56-2.56 2.551 2.551 0 0 1-2.56 2.56M46.916 31.669a2.56 2.56 0 1 1 .051-.21c-.028.066-.028.13-.051.21M44.588 25.068a2.565 2.565 0 0 1-2.672-.992 2.558 2.558 0 0 1-.102-2.845 2.564 2.564 0 0 1 4.676.764c.072.328.081.667.027 1a2.463 2.463 0 0 1-1.925 2.073M53.932 31.402a2.547 2.547 0 0 1-2.95 2.076 2.559 2.559 0 0 1-2.064-2.965 2.547 2.547 0 0 1 2.948-2.077 2.57 2.57 0 0 1 2.128 2.716.664.664 0 0 0-.05.228M51.857 25.103a2.56 2.56 0 1 1 2.108-2.945c.034.218.043.439.027.658a2.547 2.547 0 0 1-2.135 2.287M49.954 40.113a2.56 2.56 0 1 1 .314-1.037c-.02.366-.128.721-.314 1.037M48.974 16.893a2.56 2.56 0 1 1 .97-3.487c.264.446.375.965.317 1.479a2.56 2.56 0 0 1-1.287 2.008"/><path d="M26.526 52.603c-14.393 0-26.06-11.567-26.06-25.836C.466 12.498 12.133.931 26.526.931a25.936 25.936 0 0 1 15.836 5.307l-3.167 4.117A20.962 20.962 0 0 0 17.304 8.23C10.194 11.713 5.7 18.9 5.714 26.763c-.014 7.862 4.48 15.05 11.59 18.534a20.962 20.962 0 0 0 21.89-2.127l3.168 4.123a25.981 25.981 0 0 1-15.836 5.31z"/>${
config.CONSUL_BINARY_TYPE !== 'oss' && config.CONSUL_BINARY_TYPE !== ''
? `<path data-enterprise-logo d="M61 42.083h3.975v.785H61.87v2.136h2.882v.784H61.87v2.31h3.114v.785H61v-6.8zm6.907 1.018V48.9h-.828v-6.817h1.2l2.94 5.84v-5.84h.829V48.9h-1.193L67.907 43.1zm7.826-.225h-2.012v-.784h4.911v.784h-2.02V48.9h-.879v-6.024zm4.564-.793h3.975v.785h-3.106v2.136h2.882v.784h-2.882v2.31h3.114v.785h-3.992l.009-6.8zm8.605 4.347h-1.657v2.503h-.87v-6.85h2.576c1.45 0 1.963.635 1.963 1.67v.984a1.435 1.435 0 0 1-1.077 1.585l1.756 2.57h-1.002l-1.69-2.462zm0-3.562h-1.657v2.778h1.657c.828 0 1.118-.234 1.118-.901v-.968c.024-.676-.265-.901-1.094-.901l-.024-.008zm4.488-.785h2.485c1.45 0 1.963.635 1.963 1.67v1.009c0 1.05-.505 1.668-1.963 1.668H94.3v2.47h-.87l-.04-6.817zm2.419.785h-1.54v2.803h1.54c.828 0 1.118-.234 1.118-.901v-1.001c0-.668-.282-.893-1.118-.893v-.008zm6.368 3.562h-1.656v2.503h-.87v-6.85h2.576c1.45 0 1.963.635 1.963 1.67v.984a1.435 1.435 0 0 1-1.077 1.585l1.756 2.57h-1.002l-1.69-2.462zm0-3.562h-1.656v2.778h1.656c.829 0 1.118-.234 1.118-.901v-.968c.017-.676-.265-.901-1.101-.901l-.017-.008zm5.392 6.032h-.828v-6.817h.828V48.9zm4.14.1a5.76 5.76 0 0 1-2.012-.359l.141-.717c.605.195 1.236.3 1.872.308 1.085 0 1.308-.283 1.308-1.06 0-.917 0-1-1.4-1.317-1.656-.368-1.83-.685-1.83-2.095 0-1.184.49-1.76 2.162-1.76a7.648 7.648 0 0 1 1.83.225l-.074.743a8.223 8.223 0 0 0-1.74-.192c-1.11 0-1.308.225-1.308 1.01 0 .942 0 .984 1.342 1.318 1.797.45 1.888.717 1.888 2.044.033 1.176-.315 1.852-2.178 1.852zm4.332-6.917h3.95v.785h-3.105v2.136h2.882v.784h-2.882v2.31H120v.785h-3.992l.033-6.8z" fill-rule="nonzero"/>`
: ``
}<path d="M61 30.15V17.948c0-4.962 2.845-7.85 9.495-7.85 2.484 0 5.048.326 7.252.895l-.561 4.433c-2.164-.406-4.688-.691-6.53-.691-3.486 0-4.608 1.22-4.608 4.108v10.412c0 2.888 1.122 4.108 4.607 4.108 1.843 0 4.367-.284 6.53-.691l.562 4.433c-2.204.57-4.768.895-7.252.895C63.845 38 61 35.112 61 30.15zm36.808.04c0 4.068-1.802 7.81-8.493 7.81-6.69 0-8.494-3.742-8.494-7.81v-5.002c0-4.067 1.803-7.81 8.494-7.81 6.69 0 8.493 3.743 8.493 7.81v5.003zm-4.887-5.165c0-2.237-1.002-3.416-3.606-3.416s-3.606 1.18-3.606 3.416v5.328c0 2.237 1.002 3.417 3.606 3.417s3.606-1.18 3.606-3.417v-5.328zm25.79 12.568h-4.887V23.764c0-1.057-.44-1.586-1.563-1.586-1.201 0-3.325.732-5.088 1.668v13.747h-4.887V17.785h3.726l.48 1.668c2.444-1.22 5.53-2.074 7.813-2.074 3.245 0 4.407 2.318 4.407 5.857v14.357zm18.26-5.775c0 3.823-1.162 6.182-7.052 6.182-2.083 0-4.927-.488-6.73-1.139l.68-3.782c1.643.488 3.807.854 5.81.854 2.164 0 2.484-.488 2.484-1.993 0-1.22-.24-1.83-3.405-2.603-4.768-1.18-5.329-2.4-5.329-6.223 0-3.986 1.723-5.735 7.292-5.735 1.803 0 4.166.244 5.85.691l-.482 3.945c-1.482-.284-3.846-.569-5.368-.569-2.124 0-2.484.488-2.484 1.708 0 1.587.12 1.709 2.764 2.4 5.449 1.464 5.97 2.196 5.97 6.264zm4.357-14.033h4.887v13.83c0 1.057.441 1.586 1.563 1.586 1.202 0 3.325-.733 5.088-1.668V17.785h4.888v19.808h-3.726l-.481-1.667c-2.444 1.22-5.529 2.074-7.812 2.074-3.246 0-4.407-2.318-4.407-5.857V17.785zM168 37.593h-4.888V9.691L168 9v28.593z"/></g></svg>
<script src="${rootURL}assets/vendor.js"></script>
${environment === 'test' ? `<script src="${rootURL}assets/test-support.js"></script>` : ``}
<script>
var appendScript = function(src) {
var $script = document.createElement('script');
$script.src = src;
document.body.appendChild($script);
}
if(!('TextDecoder' in window)) {
appendScript('${rootURL}assets/encoding-indexes.js');
appendScript('${rootURL}assets/encoding.js');
}
</script>
<script src="${rootURL}assets/${appName}.js"></script>
<script>
CodeMirror.modeURL = {
replace: function(n, mode) {
switch(mode) {
case 'javascript':
return '${rootURL}assets/codemirror/mode/javascript/javascript.js';
case 'ruby':
return '${rootURL}assets/codemirror/mode/ruby/ruby.js';
case 'yaml':
return '${rootURL}assets/codemirror/mode/yaml/yaml.js';
}
}
};
</script>
${environment === 'test' ? `<script src="${rootURL}assets/tests.js"></script>` : ``}
`;

View File

@ -0,0 +1,37 @@
module.exports = ({ appName, environment, rootURL, config }) => `
<!-- CONSUL_VERSION: ${config.CONSUL_VERSION} -->
<script>
var setConfig = function(appName, config) {
var $meta = document.querySelector('meta[name="' + appName + '/config/environment"]');
var defaultConfig = JSON.parse(decodeURIComponent($meta.getAttribute('content')));
(
function set(blob, config) {
Object.keys(config).forEach(
function(key) {
var value = config[key];
if(Object.prototype.toString.call(value) === '[object Object]') {
set(blob[key], config[key]);
} else {
blob[key] = config[key];
}
}
);
}
)(defaultConfig, config);
$meta.setAttribute('content', encodeURIComponent(JSON.stringify(defaultConfig)));
}
setConfig(
'${appName}',
{
rootURL: '${rootURL}'
}
);
</script>
<link rel="icon" type="image/png" href="${rootURL}assets/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="${rootURL}assets/favicon-16x16.png" sizes="16x16">
<link integrity="" rel="stylesheet" href="${rootURL}assets/vendor.css">
<link integrity="" rel="stylesheet" href="${rootURL}assets/${appName}.css">
${
environment === 'test' ? `<link rel="stylesheet" href="${rootURL}assets/test-support.css">` : ``
}
`;

View File

@ -6,14 +6,9 @@
<title>ConsulUi Tests</title> <title>ConsulUi Tests</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for "head"}} {{content-for "head"}}
{{content-for "test-head"}} {{content-for "test-head"}}
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
<link rel="stylesheet" href="{{rootURL}}assets/consul-ui.css">
<link rel="stylesheet" href="{{rootURL}}assets/test-support.css">
{{content-for "head-footer"}} {{content-for "head-footer"}}
{{content-for "test-head-footer"}} {{content-for "test-head-footer"}}
</head> </head>
@ -22,10 +17,6 @@
{{content-for "test-body"}} {{content-for "test-body"}}
<script src="/testem.js" integrity=""></script> <script src="/testem.js" integrity=""></script>
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/test-support.js"></script>
<script src="{{rootURL}}assets/consul-ui.js"></script>
<script src="{{rootURL}}assets/tests.js"></script>
{{content-for "body-footer"}} {{content-for "body-footer"}}
{{content-for "test-body-footer"}} {{content-for "test-body-footer"}}

View File

@ -465,6 +465,8 @@ will exit with an error at startup.
the Web UI resources for Consul. This will automatically enable the Web UI. The directory must be the Web UI resources for Consul. This will automatically enable the Web UI. The directory must be
readable to the agent. Starting with Consul version 0.7.0 and later, the Web UI assets are included in the binary so this flag is no longer necessary; specifying only the `-ui` flag is enough to enable the Web UI. Specifying both the '-ui' and '-ui-dir' flags will result in an error. readable to the agent. Starting with Consul version 0.7.0 and later, the Web UI assets are included in the binary so this flag is no longer necessary; specifying only the `-ui` flag is enough to enable the Web UI. Specifying both the '-ui' and '-ui-dir' flags will result in an error.
* <a name="_ui_content_path"></a><a href="#_ui_content_path">`-ui-content-path`</a> - This flag provides the option to change the path the Consul UI loads from and will be displayed in the browser. By default, the path is `/ui/`, for example `http://localhost:8500/ui/`. Only alphanumerics, `-`, and `_` are allowed in a custom path. `/v1/` is not allowed as it would overwrite the API endpoint.
## <a name="configuration_files"></a>Configuration Files ## <a name="configuration_files"></a>Configuration Files
In addition to the command-line options, configuration can be put into In addition to the command-line options, configuration can be put into