Fix -ui-content-path without regex (#9569)
* Add templating to inject JSON into an application/json script tag Plus an external script in order to pick it out and inject the values we need injecting into ember's environment meta tag. The UI still uses env style naming (CONSUL_*) but we uses the new style JSON/golang props behind the scenes. Co-authored-by: Paul Banks <banks@banksco.de>
This commit is contained in:
parent
921c2a2bd8
commit
d3ecb6d7a0
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
ui: Fixes an issue with setting -ui-content-path flag/config
|
||||
```
|
File diff suppressed because one or more lines are too long
|
@ -6,10 +6,8 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"text/template"
|
||||
|
@ -244,54 +242,13 @@ func (h *Handler) renderIndex(cfg *config.RuntimeConfig, fs http.FileSystem) ([]
|
|||
}
|
||||
}
|
||||
|
||||
// Sadly we can't perform all the replacements we need with Go template
|
||||
// because some of them end up being rendered into an escaped json encoded
|
||||
// meta tag by Ember build which messes up the Go template tags. After a few
|
||||
// iterations of grossness, this seemed like the least bad for now. note we
|
||||
// have to match the encoded double quotes around the JSON string value that
|
||||
// is there as a placeholder so the end result is an actual JSON bool not a
|
||||
// string containing "false" etc.
|
||||
re := regexp.MustCompile(`%22__RUNTIME_(BOOL|STRING)_([A-Za-z0-9-_]+)__%22`)
|
||||
|
||||
content = []byte(re.ReplaceAllStringFunc(string(content), func(str string) string {
|
||||
// Trim the prefix and suffix
|
||||
pair := strings.TrimSuffix(strings.TrimPrefix(str, "%22__RUNTIME_"), "__%22")
|
||||
parts := strings.SplitN(pair, "_", 2)
|
||||
switch parts[0] {
|
||||
case "BOOL":
|
||||
if v, ok := tplData[parts[1]].(bool); ok && v {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
case "STRING":
|
||||
if v, ok := tplData[parts[1]].(string); ok {
|
||||
if bs, err := json.Marshal(v); err == nil {
|
||||
return url.PathEscape(string(bs))
|
||||
}
|
||||
// Error!
|
||||
h.logger.Error("Encoding JSON value for UI template failed",
|
||||
"placeholder", str,
|
||||
"value", v,
|
||||
)
|
||||
// Fall through to return the empty string to make JSON parse
|
||||
}
|
||||
return `""` // Empty JSON string
|
||||
}
|
||||
// Unknown type is likely an error
|
||||
h.logger.Error("Unknown placeholder type in UI template",
|
||||
"placeholder", str,
|
||||
)
|
||||
// Return a literal empty string so the JSON still parses
|
||||
return `""`
|
||||
}))
|
||||
|
||||
tpl, err := template.New("index").Funcs(template.FuncMap{
|
||||
"jsonEncodeAndEscape": func(data map[string]interface{}) (string, error) {
|
||||
bs, err := json.Marshal(data)
|
||||
"jsonEncode": func(data map[string]interface{}) (string, error) {
|
||||
bs, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed jsonEncodeAndEscape: %w", err)
|
||||
return "", fmt.Errorf("failed jsonEncode: %w", err)
|
||||
}
|
||||
return url.PathEscape(string(bs)), nil
|
||||
return string(bs), nil
|
||||
},
|
||||
}).Parse(string(content))
|
||||
if err != nil {
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
package uiserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -34,14 +36,16 @@ func TestUIServerIndex(t *testing.T) {
|
|||
path: "/", // Note /index.html redirects to /
|
||||
wantStatus: http.StatusOK,
|
||||
wantContains: []string{"<!-- CONSUL_VERSION:"},
|
||||
wantNotContains: []string{
|
||||
"__RUNTIME_BOOL_",
|
||||
"__RUNTIME_STRING_",
|
||||
},
|
||||
wantEnv: map[string]interface{}{
|
||||
"CONSUL_ACLS_ENABLED": false,
|
||||
"CONSUL_DATACENTER_LOCAL": "dc1",
|
||||
},
|
||||
wantUICfgJSON: `{
|
||||
"ACLsEnabled": false,
|
||||
"LocalDatacenter": "dc1",
|
||||
"ContentPath": "/ui/",
|
||||
"UIConfig": {
|
||||
"metrics_provider": "",
|
||||
"metrics_proxy_enabled": false,
|
||||
"dashboard_url_templates": null
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
// We do this redirect just for UI dir since the app is a single page app
|
||||
|
@ -64,24 +68,18 @@ func TestUIServerIndex(t *testing.T) {
|
|||
wantContains: []string{
|
||||
"<!-- CONSUL_VERSION:",
|
||||
},
|
||||
wantNotContains: []string{
|
||||
// This is a quick check to be sure that we actually URL encoded the
|
||||
// JSON ui settings too. The assertions below could pass just fine even
|
||||
// if we got that wrong because the decode would be a no-op if it wasn't
|
||||
// URL encoded. But this just ensures that we don't see the raw values
|
||||
// in the output because the quotes should be encoded.
|
||||
`"a-very-unlikely-string"`,
|
||||
},
|
||||
wantEnv: map[string]interface{}{
|
||||
"CONSUL_ACLS_ENABLED": false,
|
||||
},
|
||||
wantUICfgJSON: `{
|
||||
"ACLsEnabled": false,
|
||||
"LocalDatacenter": "dc1",
|
||||
"ContentPath": "/ui/",
|
||||
"UIConfig": {
|
||||
"metrics_provider": "foo",
|
||||
"metrics_provider_options": {
|
||||
"a-very-unlikely-string":1
|
||||
},
|
||||
"metrics_proxy_enabled": false,
|
||||
"dashboard_url_templates": null
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
|
@ -90,9 +88,16 @@ func TestUIServerIndex(t *testing.T) {
|
|||
path: "/",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContains: []string{"<!-- CONSUL_VERSION:"},
|
||||
wantEnv: map[string]interface{}{
|
||||
"CONSUL_ACLS_ENABLED": true,
|
||||
},
|
||||
wantUICfgJSON: `{
|
||||
"ACLsEnabled": true,
|
||||
"LocalDatacenter": "dc1",
|
||||
"ContentPath": "/ui/",
|
||||
"UIConfig": {
|
||||
"metrics_provider": "",
|
||||
"metrics_proxy_enabled": false,
|
||||
"dashboard_url_templates": null
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "external transformation",
|
||||
|
@ -110,13 +115,16 @@ func TestUIServerIndex(t *testing.T) {
|
|||
wantContains: []string{
|
||||
"<!-- CONSUL_VERSION:",
|
||||
},
|
||||
wantEnv: map[string]interface{}{
|
||||
"CONSUL_SSO_ENABLED": true,
|
||||
},
|
||||
wantUICfgJSON: `{
|
||||
"ACLsEnabled": false,
|
||||
"SSOEnabled": true,
|
||||
"LocalDatacenter": "dc1",
|
||||
"ContentPath": "/ui/",
|
||||
"UIConfig": {
|
||||
"metrics_provider": "bar",
|
||||
"metrics_proxy_enabled": false,
|
||||
"dashboard_url_templates": null
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
|
@ -148,13 +156,6 @@ func TestUIServerIndex(t *testing.T) {
|
|||
for _, want := range tc.wantContains {
|
||||
require.Contains(t, rec.Body.String(), want)
|
||||
}
|
||||
for _, wantNot := range tc.wantNotContains {
|
||||
require.NotContains(t, rec.Body.String(), wantNot)
|
||||
}
|
||||
env := extractEnv(t, rec.Body.String())
|
||||
for k, v := range tc.wantEnv {
|
||||
require.Equal(t, v, env[k])
|
||||
}
|
||||
if tc.wantUICfgJSON != "" {
|
||||
require.JSONEq(t, tc.wantUICfgJSON, extractUIConfig(t, rec.Body.String()))
|
||||
}
|
||||
|
@ -162,41 +163,48 @@ func TestUIServerIndex(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func extractMetaJSON(t *testing.T, name, content string) string {
|
||||
func extractApplicationJSON(t *testing.T, attrName, content string) string {
|
||||
t.Helper()
|
||||
|
||||
// Find and extract the env meta tag. Why yes I _am_ using regexp to parse
|
||||
// HTML thanks for asking. In this case it's HTML with a very limited format
|
||||
// so I don't feel too bad but maybe I should.
|
||||
// https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454
|
||||
re := regexp.MustCompile(`<meta name="` + name + `+" content="([^"]*)"`)
|
||||
var scriptContent *html.Node
|
||||
var find func(node *html.Node)
|
||||
|
||||
matches := re.FindStringSubmatch(content)
|
||||
require.Len(t, matches, 2, "didn't find the %s meta tag", name)
|
||||
|
||||
// Unescape the JSON
|
||||
jsonStr, err := url.PathUnescape(matches[1])
|
||||
require.NoError(t, err)
|
||||
|
||||
return jsonStr
|
||||
// Recurse down the tree and pick out <script attrName=ourAttrName>
|
||||
find = func(node *html.Node) {
|
||||
if node.Type == html.ElementNode && node.Data == "script" {
|
||||
for i := 0; i < len(node.Attr); i++ {
|
||||
attr := node.Attr[i]
|
||||
if attr.Key == attrName {
|
||||
// find the script and save off the content, which in this case is
|
||||
// the JSON we are looking for, once we have it finish up
|
||||
scriptContent = node.FirstChild
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
find(child)
|
||||
}
|
||||
}
|
||||
|
||||
func extractEnv(t *testing.T, content string) map[string]interface{} {
|
||||
t.Helper()
|
||||
|
||||
js := extractMetaJSON(t, "consul-ui/config/environment", content)
|
||||
|
||||
var env map[string]interface{}
|
||||
|
||||
err := json.Unmarshal([]byte(js), &env)
|
||||
doc, err := html.Parse(strings.NewReader(content))
|
||||
require.NoError(t, err)
|
||||
|
||||
return env
|
||||
find(doc)
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := io.Writer(&buf)
|
||||
|
||||
renderErr := html.Render(w, scriptContent)
|
||||
require.NoError(t, renderErr)
|
||||
|
||||
jsonStr := html.UnescapeString(buf.String())
|
||||
return jsonStr
|
||||
}
|
||||
|
||||
func extractUIConfig(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
return extractMetaJSON(t, "consul-ui/ui_config", content)
|
||||
return extractApplicationJSON(t, "data-consul-ui-config", content)
|
||||
}
|
||||
|
||||
type cfgFunc func(cfg *config.RuntimeConfig)
|
||||
|
|
|
@ -43,7 +43,10 @@ export default function(config = {}, win = window, doc = document) {
|
|||
return {};
|
||||
}
|
||||
};
|
||||
const ui_config = JSON.parse(unescape(doc.getElementsByName('consul-ui/ui_config')[0].content));
|
||||
const operatorConfig = JSON.parse(
|
||||
doc.querySelector(`[data-${config.modulePrefix}-config]`).textContent
|
||||
);
|
||||
const ui_config = operatorConfig.UIConfig || {};
|
||||
const scripts = doc.getElementsByTagName('script');
|
||||
// we use the currently executing script as a reference
|
||||
// to figure out where we are for other things such as
|
||||
|
@ -57,6 +60,18 @@ export default function(config = {}, win = window, doc = document) {
|
|||
const operator = function(str, env) {
|
||||
let protocol, dashboards, provider, proxy;
|
||||
switch (str) {
|
||||
case 'CONSUL_NSPACES_ENABLED':
|
||||
return typeof operatorConfig.NamespacesEnabled === 'undefined'
|
||||
? false
|
||||
: operatorConfig.NamespacesEnabled;
|
||||
case 'CONSUL_SSO_ENABLED':
|
||||
return typeof operatorConfig.SSOEnabled === 'undefined' ? false : operatorConfig.SSOEnabled;
|
||||
case 'CONSUL_ACLS_ENABLED':
|
||||
return typeof operatorConfig.ACLsEnabled === 'undefined'
|
||||
? false
|
||||
: operatorConfig.ACLsEnabled;
|
||||
case 'CONSUL_DATACENTER_LOCAL':
|
||||
return operatorConfig.LocalDatacenter;
|
||||
case 'CONSUL_UI_CONFIG':
|
||||
dashboards = {};
|
||||
provider = env('CONSUL_METRICS_PROVIDER');
|
||||
|
@ -155,6 +170,10 @@ export default function(config = {}, win = window, doc = document) {
|
|||
// these are strings
|
||||
return user(str) || ui(str);
|
||||
case 'CONSUL_UI_CONFIG':
|
||||
case 'CONSUL_DATACENTER_LOCAL':
|
||||
case 'CONSUL_ACLS_ENABLED':
|
||||
case 'CONSUL_NSPACES_ENABLED':
|
||||
case 'CONSUL_SSO_ENABLED':
|
||||
case 'CONSUL_METRICS_PROVIDER':
|
||||
case 'CONSUL_METRICS_PROXY_ENABLE':
|
||||
case 'CONSUL_SERVICE_DASHBOARD_URL':
|
||||
|
|
|
@ -92,11 +92,13 @@ module.exports = function(environment, $ = process.env) {
|
|||
CONSUL_UI_DISABLE_ANCHOR_SELECTION: env('CONSUL_UI_DISABLE_ANCHOR_SELECTION', false),
|
||||
|
||||
// The following variables are runtime variables that are overwritten when
|
||||
// the go binary services the index.html page
|
||||
CONSUL_ACLS_ENABLED: false,
|
||||
CONSUL_NSPACES_ENABLED: false,
|
||||
CONSUL_SSO_ENABLED: false,
|
||||
CONSUL_DATACENTER_LOCAL: env('CONSUL_DATACENTER_LOCAL', 'dc1'),
|
||||
// the go binary serves the index.html page
|
||||
operatorConfig: {
|
||||
ACLsEnabled: false,
|
||||
NamespacesEnabled: false,
|
||||
SSOEnabled: false,
|
||||
LocalDatacenter: env('CONSUL_DATACENTER_LOCAL', 'dc1'),
|
||||
},
|
||||
|
||||
// Static variables used in multiple places throughout the UI
|
||||
CONSUL_HOME_URL: 'https://www.consul.io',
|
||||
|
@ -112,9 +114,12 @@ module.exports = function(environment, $ = process.env) {
|
|||
locationType: 'none',
|
||||
|
||||
// During testing ACLs default to being turned on
|
||||
CONSUL_ACLS_ENABLED: env('CONSUL_ACLS_ENABLED', true),
|
||||
CONSUL_NSPACES_ENABLED: env('CONSUL_NSPACES_ENABLED', false),
|
||||
CONSUL_SSO_ENABLED: env('CONSUL_SSO_ENABLED', false),
|
||||
operatorConfig: {
|
||||
ACLsEnabled: env('CONSUL_ACLS_ENABLED', true),
|
||||
NamespacesEnabled: env('CONSUL_NSPACES_ENABLED', false),
|
||||
SSOEnabled: env('CONSUL_SSO_ENABLED', false),
|
||||
LocalDatacenter: env('CONSUL_DATACENTER_LOCAL', 'dc1'),
|
||||
},
|
||||
|
||||
'@hashicorp/ember-cli-api-double': {
|
||||
'auto-import': false,
|
||||
|
@ -143,9 +148,12 @@ module.exports = function(environment, $ = process.env) {
|
|||
// different staging sites can be built with certain features disabled
|
||||
// by setting an environment variable to 0 during building (e.g.
|
||||
// CONSUL_NSPACES_ENABLED=0 make build)
|
||||
CONSUL_ACLS_ENABLED: env('CONSUL_ACLS_ENABLED', true),
|
||||
CONSUL_NSPACES_ENABLED: env('CONSUL_NSPACES_ENABLED', true),
|
||||
CONSUL_SSO_ENABLED: env('CONSUL_SSO_ENABLED', true),
|
||||
operatorConfig: {
|
||||
ACLsEnabled: env('CONSUL_ACLS_ENABLED', true),
|
||||
NamespacesEnabled: env('CONSUL_NSPACES_ENABLED', true),
|
||||
SSOEnabled: env('CONSUL_SSO_ENABLED', true),
|
||||
LocalDatacenter: env('CONSUL_DATACENTER_LOCAL', 'dc1'),
|
||||
},
|
||||
|
||||
'@hashicorp/ember-cli-api-double': {
|
||||
enabled: true,
|
||||
|
@ -157,22 +165,9 @@ module.exports = function(environment, $ = process.env) {
|
|||
break;
|
||||
case environment === 'production':
|
||||
ENV = Object.assign({}, ENV, {
|
||||
// These values are placeholders that are replaced when Consul renders
|
||||
// the index.html based on runtime config. They can't use Go template
|
||||
// syntax since this object ends up JSON and URLencoded in an HTML meta
|
||||
// tag which obscured the Go template tag syntax.
|
||||
//
|
||||
// __RUNTIME_BOOL_Xxxx__ will be replaced with either "true" or "false"
|
||||
// depending on whether the named variable is true or false in the data
|
||||
// returned from `uiTemplateDataFromConfig`.
|
||||
//
|
||||
// __RUNTIME_STRING_Xxxx__ will be replaced with the literal string in
|
||||
// the named variable in the data returned from
|
||||
// `uiTemplateDataFromConfig`. It may be empty.
|
||||
CONSUL_ACLS_ENABLED: '__RUNTIME_BOOL_ACLsEnabled__',
|
||||
CONSUL_SSO_ENABLED: '__RUNTIME_BOOL_SSOEnabled__',
|
||||
CONSUL_NSPACES_ENABLED: '__RUNTIME_BOOL_NamespacesEnabled__',
|
||||
CONSUL_DATACENTER_LOCAL: '__RUNTIME_STRING_LocalDatacenter__',
|
||||
// in production operatorConfig is populated at consul runtime from
|
||||
// operator configuration
|
||||
operatorConfig: {},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -116,6 +116,9 @@ module.exports = function(defaults) {
|
|||
app.import('vendor/metrics-providers/prometheus.js', {
|
||||
outputFile: 'assets/metrics-providers/prometheus.js',
|
||||
});
|
||||
app.import('vendor/init.js', {
|
||||
outputFile: 'assets/init.js',
|
||||
});
|
||||
let tree = app.toTree();
|
||||
return tree;
|
||||
};
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// rootURL in production equals `{{.ContentPath}}` and therefore is replaced
|
||||
// with the value of -ui-content-path. During development rootURL uses the
|
||||
// value as set in environment.js
|
||||
module.exports = ({ appName, environment, rootURL, config }) => `
|
||||
<noscript>
|
||||
<div style="margin: 0 auto;">
|
||||
|
@ -5,11 +8,16 @@ module.exports = ({ appName, environment, rootURL, config }) => `
|
|||
<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 type="application/json" data-consul-ui-config>
|
||||
${environment === 'production' ? `{{jsonEncode .}}` : JSON.stringify(config.operatorConfig)}
|
||||
</script>
|
||||
<script src="${rootURL}assets/init.js"></script>
|
||||
<script src="${rootURL}assets/vendor.js"></script>
|
||||
${environment === 'test' ? `<script src="${rootURL}assets/test-support.js"></script>` : ``}
|
||||
<script>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// rootURL in production equals `{{.ContentPath}}` and therefore is replaced
|
||||
// with the value of -ui-content-path. During development rootURL uses the
|
||||
// value as set in environment.js
|
||||
|
||||
module.exports = ({ appName, environment, rootURL, config }) => `
|
||||
<!-- CONSUL_VERSION: ${config.CONSUL_VERSION} -->
|
||||
<meta name="consul-ui/ui_config" content="${
|
||||
environment === 'production'
|
||||
? `{{ jsonEncodeAndEscape .UIConfig }}`
|
||||
: escape(JSON.stringify({}))
|
||||
}" />
|
||||
|
||||
<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">
|
||||
|
|
|
@ -11,16 +11,17 @@ test(
|
|||
{
|
||||
environment: 'production',
|
||||
CONSUL_BINARY_TYPE: 'oss',
|
||||
CONSUL_ACLS_ENABLED: '__RUNTIME_BOOL_ACLsEnabled__',
|
||||
CONSUL_SSO_ENABLED: '__RUNTIME_BOOL_SSOEnabled__',
|
||||
CONSUL_NSPACES_ENABLED: '__RUNTIME_BOOL_NamespacesEnabled__',
|
||||
operatorConfig: {}
|
||||
},
|
||||
{
|
||||
environment: 'test',
|
||||
CONSUL_BINARY_TYPE: 'oss',
|
||||
CONSUL_ACLS_ENABLED: true,
|
||||
CONSUL_NSPACES_ENABLED: false,
|
||||
CONSUL_SSO_ENABLED: false,
|
||||
operatorConfig: {
|
||||
ACLsEnabled: true,
|
||||
NamespacesEnabled: false,
|
||||
SSOEnabled: false,
|
||||
LocalDatacenter: 'dc1',
|
||||
}
|
||||
},
|
||||
{
|
||||
$: {
|
||||
|
@ -28,9 +29,12 @@ test(
|
|||
},
|
||||
environment: 'test',
|
||||
CONSUL_BINARY_TYPE: 'oss',
|
||||
CONSUL_ACLS_ENABLED: true,
|
||||
CONSUL_NSPACES_ENABLED: true,
|
||||
CONSUL_SSO_ENABLED: false,
|
||||
operatorConfig: {
|
||||
ACLsEnabled: true,
|
||||
NamespacesEnabled: true,
|
||||
SSOEnabled: false,
|
||||
LocalDatacenter: 'dc1',
|
||||
}
|
||||
},
|
||||
{
|
||||
$: {
|
||||
|
@ -38,16 +42,22 @@ test(
|
|||
},
|
||||
environment: 'test',
|
||||
CONSUL_BINARY_TYPE: 'oss',
|
||||
CONSUL_ACLS_ENABLED: true,
|
||||
CONSUL_NSPACES_ENABLED: false,
|
||||
CONSUL_SSO_ENABLED: true,
|
||||
operatorConfig: {
|
||||
ACLsEnabled: true,
|
||||
NamespacesEnabled: false,
|
||||
SSOEnabled: true,
|
||||
LocalDatacenter: 'dc1',
|
||||
}
|
||||
},
|
||||
{
|
||||
environment: 'staging',
|
||||
CONSUL_BINARY_TYPE: 'oss',
|
||||
CONSUL_ACLS_ENABLED: true,
|
||||
CONSUL_NSPACES_ENABLED: true,
|
||||
CONSUL_SSO_ENABLED: true,
|
||||
operatorConfig: {
|
||||
ACLsEnabled: true,
|
||||
NamespacesEnabled: true,
|
||||
SSOEnabled: true,
|
||||
LocalDatacenter: 'dc1',
|
||||
}
|
||||
}
|
||||
].forEach(
|
||||
function(item) {
|
||||
|
@ -57,7 +67,7 @@ test(
|
|||
if(key === '$') {
|
||||
return;
|
||||
}
|
||||
t.equal(
|
||||
t.deepEqual(
|
||||
env[key],
|
||||
item[key],
|
||||
`Expect ${key} to equal ${item[key]} in the ${item.environment} environment ${typeof item.$ !== 'undefined' ? `(with ${JSON.stringify(item.$)})` : ''}`
|
||||
|
|
|
@ -19,6 +19,11 @@ const makeGetElementsBy = function(str) {
|
|||
];
|
||||
};
|
||||
};
|
||||
const makeOperatorConfig = function(json) {
|
||||
return {
|
||||
textContent: JSON.stringify(json),
|
||||
};
|
||||
};
|
||||
const win = {
|
||||
performance: {
|
||||
getEntriesByType: getEntriesByType,
|
||||
|
@ -34,6 +39,7 @@ const doc = {
|
|||
cookie: '',
|
||||
getElementsByTagName: makeGetElementsBy(''),
|
||||
getElementsByName: makeGetElementsBy('{}'),
|
||||
querySelector: () => makeOperatorConfig({}),
|
||||
};
|
||||
module('Unit | Utility | getEnvironment', function() {
|
||||
test('it returns a function', function(assert) {
|
||||
|
@ -62,6 +68,7 @@ module('Unit | Utility | getEnvironment', function() {
|
|||
cookie: '',
|
||||
getElementsByTagName: makeGetElementsBy(`${expected}/assets/consul-ui.js`),
|
||||
getElementsByName: makeGetElementsBy('{}'),
|
||||
querySelector: () => makeOperatorConfig({}),
|
||||
};
|
||||
let env = getEnvironment(config, win, doc);
|
||||
assert.equal(env('CONSUL_BASE_UI_URL'), expected);
|
||||
|
@ -70,6 +77,7 @@ module('Unit | Utility | getEnvironment', function() {
|
|||
cookie: '',
|
||||
getElementsByTagName: makeGetElementsBy(`${expected}/assets/consul-ui.js`),
|
||||
getElementsByName: makeGetElementsBy('{}'),
|
||||
querySelector: () => makeOperatorConfig({}),
|
||||
};
|
||||
env = getEnvironment(config, win, doc);
|
||||
assert.equal(env('CONSUL_BASE_UI_URL'), expected);
|
||||
|
@ -144,6 +152,7 @@ module('Unit | Utility | getEnvironment', function() {
|
|||
cookie: 'CONSUL_NSPACES_ENABLE=1',
|
||||
getElementsByTagName: makeGetElementsBy(''),
|
||||
getElementsByName: makeGetElementsBy('{}'),
|
||||
querySelector: () => makeOperatorConfig({ NamespacesEnabled: true }),
|
||||
};
|
||||
let env = getEnvironment(config, win, doc);
|
||||
assert.ok(env('CONSUL_NSPACES_ENABLED'));
|
||||
|
@ -155,6 +164,7 @@ module('Unit | Utility | getEnvironment', function() {
|
|||
cookie: 'CONSUL_NSPACES_ENABLE=0',
|
||||
getElementsByTagName: makeGetElementsBy(''),
|
||||
getElementsByName: makeGetElementsBy('{}'),
|
||||
querySelector: () => makeOperatorConfig({ NamespacesEnabled: false }),
|
||||
};
|
||||
env = getEnvironment(config, win, doc);
|
||||
assert.notOk(env('CONSUL_NSPACES_ENABLED'));
|
||||
|
@ -180,6 +190,7 @@ module('Unit | Utility | getEnvironment', function() {
|
|||
cookie: 'CONSUL_NSPACES_ENABLE=1',
|
||||
getElementsByTagName: makeGetElementsBy(''),
|
||||
getElementsByName: makeGetElementsBy('{}'),
|
||||
querySelector: () => makeOperatorConfig({ NamespacesEnabled: false }),
|
||||
};
|
||||
let env = getEnvironment(config, win, doc);
|
||||
assert.notOk(env('CONSUL_NSPACES_ENABLED'));
|
||||
|
@ -191,6 +202,7 @@ module('Unit | Utility | getEnvironment', function() {
|
|||
cookie: 'CONSUL_NSPACES_ENABLE=0',
|
||||
getElementsByTagName: makeGetElementsBy(''),
|
||||
getElementsByName: makeGetElementsBy('{}'),
|
||||
querySelector: () => makeOperatorConfig({ NamespacesEnabled: true }),
|
||||
};
|
||||
env = getEnvironment(config, win, doc);
|
||||
assert.ok(env('CONSUL_NSPACES_ENABLED'));
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
(function(doc, appName) {
|
||||
try {
|
||||
const $appMeta = doc.querySelector(`[name="${appName}/config/environment"]`);
|
||||
// pick out the operatorConfig from our application/json script tag
|
||||
const operatorConfig = JSON.parse(doc.querySelector(`[data-${appName}-config]`).textContent);
|
||||
// pick out the ember config from its meta tag
|
||||
const emberConfig = JSON.parse(decodeURIComponent($appMeta.getAttribute('content')));
|
||||
|
||||
// rootURL is a special variable that requires settings before ember
|
||||
// boots via ember's HTML metadata tag, the variable is equivalent to
|
||||
// the -ui-content-path Consul flag (or `ui_config { content_path = ""}`)
|
||||
// There will potentially be one or two more 'pre-init' variables that we need.
|
||||
// Anything not 'pre-init' should use ui_config.
|
||||
// Check the value to make sure its there and a string
|
||||
const rootURL =
|
||||
typeof operatorConfig.ContentPath !== 'string' ? '' : operatorConfig.ContentPath;
|
||||
if (rootURL.length > 0) {
|
||||
emberConfig.rootURL = rootURL;
|
||||
}
|
||||
$appMeta.setAttribute('content', encodeURIComponent(JSON.stringify(emberConfig)));
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to parse ${appName} settings: ${e.message}`);
|
||||
}
|
||||
})(document, 'consul-ui');
|
|
@ -484,6 +484,8 @@ golang.org/x/lint/golint
|
|||
golang.org/x/net/bpf
|
||||
golang.org/x/net/context
|
||||
golang.org/x/net/context/ctxhttp
|
||||
golang.org/x/net/html
|
||||
golang.org/x/net/html/atom
|
||||
golang.org/x/net/http/httpguts
|
||||
golang.org/x/net/http2
|
||||
golang.org/x/net/http2/hpack
|
||||
|
|
Loading…
Reference in New Issue