Merge pull request #5422 from spalger/backport/5337

Backport 5337 and 5410
This commit is contained in:
Spencer 2015-11-17 13:12:21 -06:00
commit 81cd36e319
23 changed files with 226 additions and 23 deletions

View file

@ -4,6 +4,10 @@
# The host to bind the server to.
# server.host: "0.0.0.0"
# If you are running kibana behind a proxy, and want to mount it at a path,
# specify that path here. The basePath can't end in a slash.
# server.basePath: ""
# A value to use as a XSRF token. This token is sent back to the server on each request
# and required if you want to execute requests from other clients (like curl).
# server.xsrf.token: ""

View file

@ -16,6 +16,7 @@ let babelExclude = [/[\/\\](webpackShims|node_modules|bower_components)[\/\\]/];
class BaseOptimizer {
constructor(opts) {
this.env = opts.env;
this.urlBasePath = opts.urlBasePath;
this.bundles = opts.bundles;
this.profile = opts.profile || false;
@ -74,7 +75,7 @@ class BaseOptimizer {
path: this.env.workingDir,
filename: '[name].bundle.js',
sourceMapFilename: '[file].map',
publicPath: '/bundles/',
publicPath: `${this.urlBasePath || ''}/bundles/`,
devtoolModuleFilenameTemplate: '[absolute-resource-path]'
},

View file

@ -38,6 +38,7 @@ module.exports = async (kbnServer, server, config) => {
env: bundles.env,
bundles: bundles,
profile: config.get('optimize.profile'),
urlBasePath: config.get('server.basePath'),
sourceMaps: config.get('optimize.sourceMaps'),
unsafeCache: config.get('optimize.unsafeCache'),
});

View file

@ -15,6 +15,7 @@ module.exports = async (kbnServer, kibanaHapiServer, config) => {
profile: config.get('optimize.profile'),
sourceMaps: config.get('optimize.sourceMaps'),
prebuild: config.get('optimize.lazyPrebuild'),
urlBasePath: config.get('server.basePath'),
unsafeCache: config.get('optimize.unsafeCache'),
})
);

View file

@ -3,7 +3,7 @@
<div class="container-fluid">
<ul class="nav navbar-nav">
<li ng-repeat="section in sections" ng-class="section.class">
<a class="navbar-link" ng-href="{{section.url}}">{{section.display}}</a>
<a class="navbar-link" kbn-href="{{section.url}}">{{section.display}}</a>
</li>
</ul>
</div>

View file

@ -2,7 +2,7 @@
<div class="kbn-settings-about container" ng-controller="settingsAbout">
<div class="col-md-4 col-md-offset-4 jumbotron">
<center>
<img src='/plugins/kibana/settings/sections/about/barcode.svg' alt="Kibana Barcode Logo" width="128" height="128"><br>
<img kbn-src="/plugins/kibana/settings/sections/about/barcode.svg" alt="Kibana Barcode Logo" width="128" height="128"><br>
<h1>Kibana</h1>
<p>
<table class="table table-condensed kbn-settings-about-versions">

View file

@ -5,7 +5,7 @@ var notify = require('ui/notify');
require('plugins/statusPage/statusPageMetric');
require('plugins/statusPage/statusPage.less');
require('ui/chrome')
var chrome = require('ui/chrome')
.setTabs([
{
id: '',
@ -23,7 +23,7 @@ require('ui/chrome')
// go ahead and get the info you want
return $http
.get('/api/status')
.get(chrome.addBasePath('/api/status'))
.then(function (resp) {
if (ui.fetchError) {

View file

@ -83,4 +83,18 @@ module.exports = class KbnServer {
async close() {
await fromNode(cb => this.server.stop(cb));
}
async inject(opts) {
if (!this.server) await this.ready();
return await fromNode(cb => {
try {
this.server.inject(opts, (resp) => {
cb(null, resp);
});
} catch (err) {
cb(err);
}
});
}
};

View file

@ -0,0 +1,38 @@
import { resolve } from 'path';
import { fromNode as fn } from 'bluebird';
import expect from 'expect.js';
import KbnServer from '../KbnServer';
const src = resolve.bind(__dirname, '../../');
const basePath = '/kibana';
describe('Server basePath config', function () {
this.slow(10000);
this.timeout(60000);
let kbnServer;
before(async function () {
kbnServer = new KbnServer({
server: { autoListen: false, basePath },
plugins: { scanDirs: [src('plugins')] },
logging: { quiet: true },
optimize: { enabled: false },
});
await kbnServer.ready();
return kbnServer;
});
after(async function () {
await kbnServer.close();
});
it('appends the basePath to root redirect', async function () {
const response = await kbnServer.inject({
url: '/',
method: 'GET'
});
expect(response.payload).to.match(/defaultRoute = '\/kibana\/app\/kibana'/);
});
});

View file

@ -0,0 +1,38 @@
import schemaProvider from '../schema';
import expect from 'expect.js';
import Joi from 'joi';
describe('Config schema', function () {
let schema;
beforeEach(() => schema = schemaProvider());
function validate(data, options) {
return Joi.validate(data, schema, options);
}
describe('server', function () {
describe('basePath', function () {
it('accepts empty strings', function () {
const { error } = validate({ server: { basePath: '' }});
expect(error == null).to.be.ok();
});
it('accepts strings with leading slashes', function () {
const { error } = validate({ server: { basePath: '/path' }});
expect(error == null).to.be.ok();
});
it('rejects strings with trailing slashes', function () {
const { error } = validate({ server: { basePath: '/path/' }});
expect(error).to.have.property('details');
expect(error.details[0]).to.have.property('path', 'server.basePath');
});
it('rejects strings without leading slashes', function () {
const { error } = validate({ server: { basePath: 'path' }});
expect(error).to.have.property('details');
expect(error.details[0]).to.have.property('path', 'server.basePath');
});
});
});
});

View file

@ -30,6 +30,7 @@ module.exports = () => Joi.object({
port: Joi.number().default(5601),
autoListen: Joi.boolean().default(true),
defaultRoute: Joi.string(),
basePath: Joi.string().default('').allow('').regex(/(^$|^\/.*[^\/]$)/, `start with a slash, don't end with one`),
ssl: Joi.object({
cert: Joi.string(),
key: Joi.string()

View file

@ -1,11 +1,10 @@
let _ = require('lodash');
module.exports = _.once(function (kbnServer) {
const { uiExports, config } = kbnServer;
// user configured default route
let defaultConfig = kbnServer.config.get('server.defaultRoute');
let defaultConfig = config.get('server.defaultRoute');
if (defaultConfig) return defaultConfig;
// redirect to the single app
let apps = kbnServer.uiExports.apps.toArray();
return '/app/kibana';
return `${config.get('server.basePath')}/app/kibana`;
});

View file

@ -97,7 +97,7 @@ module.exports = function (kbnServer, server, config) {
method: 'GET',
handler: function (req, reply) {
return reply.view('rootRedirect', {
hashRoute: '/app/kibana',
hashRoute: `${config.get('server.basePath')}/app/kibana`,
defaultRoute: getDefaultRoute(kbnServer),
});
}

View file

@ -19,7 +19,7 @@ class UiApp {
this.hidden = this.spec.hidden;
this.autoloadOverrides = this.spec.autoload;
this.templateName = this.spec.templateName || 'uiApp';
this.url = this.spec.url || '/app/' + this.id;
this.url = `${spec.urlBasePath || ''}${this.spec.url || `/app/${this.id}`}`;
// once this resolves, no reason to run it again
this.getModules = _.once(this.getModules);

View file

@ -4,9 +4,10 @@ var minimatch = require('minimatch');
var UiAppCollection = require('./UiAppCollection');
class UiExports {
constructor(kbnServer) {
constructor({ urlBasePath }) {
this.apps = new UiAppCollection(this);
this.aliases = {};
this.urlBasePath = urlBasePath;
this.exportConsumer = _.memoize(this.exportConsumer);
this.consumers = [];
this.bundleProviders = [];
@ -48,7 +49,10 @@ class UiExports {
case 'apps':
return (plugin, specs) => {
for (let spec of [].concat(specs || [])) {
let app = this.apps.new(_.defaults({}, spec, { id: plugin.id }));
let app = this.apps.new(_.defaults({}, spec, {
id: plugin.id,
urlBasePath: this.urlBasePath
}));
plugin.apps.add(app);
}
};

View file

@ -12,10 +12,13 @@ module.exports = async (kbnServer, server, config) => {
let UiBundlerEnv = require('./UiBundlerEnv');
let loadingGif = readFile(fromRoot('src/ui/public/loading.gif'), { encoding: 'base64'});
let uiExports = kbnServer.uiExports = new UiExports(kbnServer);
let uiExports = kbnServer.uiExports = new UiExports({
urlBasePath: config.get('server.basePath')
});
let bundlerEnv = new UiBundlerEnv(config.get('optimize.bundleDir'));
bundlerEnv.addContext('env', config.get('env.name'));
bundlerEnv.addContext('urlBasePath', config.get('server.basePath'));
bundlerEnv.addContext('sourceMaps', config.get('optimize.sourceMaps'));
bundlerEnv.addContext('kbnVersion', config.get('pkg.version'));
bundlerEnv.addContext('buildNum', config.get('pkg.buildNum'));
@ -70,6 +73,7 @@ module.exports = async (kbnServer, server, config) => {
version: kbnServer.version,
buildNum: config.get('pkg.buildNum'),
buildSha: config.get('pkg.buildSha'),
basePath: config.get('server.basePath'),
vars: defaults(app.getInjectedVars(), defaultInjectedVars),
xsrfToken: this.issueXsrfToken(),
};
@ -77,7 +81,8 @@ module.exports = async (kbnServer, server, config) => {
return this.view(app.templateName, {
app: app,
loadingGif: loadingGif,
kibanaPayload: payload
kibanaPayload: payload,
bundlePath: `${config.get('server.basePath')}/bundles`,
});
});
};

View file

@ -0,0 +1,40 @@
import expect from 'expect.js';
import initChromeNavApi from 'ui/chrome/api/nav';
const basePath = '/someBasePath';
function getChrome(customInternals = { basePath }) {
const chrome = {};
initChromeNavApi(chrome, {
nav: [],
...customInternals,
});
return chrome;
}
describe('chrome nav apis', function () {
describe('#getBasePath()', function () {
it('returns the basePath', function () {
const chrome = getChrome();
expect(chrome.getBasePath()).to.be(basePath);
});
});
describe('#addBasePath()', function () {
it('returns undefined when nothing is passed', function () {
const chrome = getChrome();
expect(chrome.addBasePath()).to.be(undefined);
});
it('prepends the base path when the input is a path', function () {
const chrome = getChrome();
expect(chrome.addBasePath('/other/path')).to.be(`${basePath}/other/path`);
});
it('ignores non-path urls', function () {
const chrome = getChrome();
expect(chrome.addBasePath('http://github.com/elastic/kibana')).to.be('http://github.com/elastic/kibana');
});
});
});

View file

@ -21,7 +21,7 @@ module.exports = function (chrome, internals) {
.value('sessionId', Date.now())
.value('esUrl', (function () {
var a = document.createElement('a');
a.href = '/elasticsearch';
a.href = chrome.addBasePath('/elasticsearch');
return a.href;
}()))
.config(chrome.$setupXsrfRequestInterceptor)

View file

@ -1,6 +1,7 @@
module.exports = function (chrome, internals) {
const { startsWith } = require('lodash');
const { startsWith, isString } = require('lodash');
import { parse, format } from 'url';
export default function (chrome, internals) {
chrome.getNavLinks = function () {
return internals.nav;
};
@ -9,6 +10,30 @@ module.exports = function (chrome, internals) {
return internals.appUrlStore.getItem(`lastSubUrl:${url}`);
};
chrome.getBasePath = function () {
return internals.basePath || '';
};
chrome.addBasePath = function (url) {
var isUrl = url && isString(url);
if (!isUrl) return url;
var parsed = parse(url);
if (!parsed.host && parsed.pathname) {
if (parsed.pathname[0] === '/') {
parsed.pathname = chrome.getBasePath() + parsed.pathname;
}
}
return format({
protocol: parsed.protocol,
host: parsed.host,
pathname: parsed.pathname,
query: parsed.query,
hash: parsed.hash,
});
};
internals.trackPossibleSubUrl = function (url) {
for (const link of internals.nav) {
if (startsWith(url, link.url)) {

View file

@ -9,12 +9,12 @@ require('ui/private');
require('ui/promises');
var metadata = require('ui/metadata');
var TabCollection = require('ui/chrome/TabCollection');
var chrome = {};
var internals = _.defaults(
_.cloneDeep(metadata),
{
basePath: '',
rootController: null,
rootTemplate: null,
showAppsLink: null,

View file

@ -0,0 +1,23 @@
import UiModules from 'ui/modules';
import chrome from 'ui/chrome';
import { words, camelCase, kebabCase } from 'lodash';
export function kbnUrlDirective(name) {
const srcAttr = kebabCase(name);
const attr = kebabCase(words(name).slice(1));
UiModules
.get('kibana')
.directive(name, function (Private) {
return {
restrict: 'A',
link: function ($scope, $el, $attr) {
$attr.$observe(name, function (val) {
$attr.$set(attr, chrome.addBasePath(val));
});
}
};
});
}
kbnUrlDirective('kbnHref');

View file

@ -0,0 +1,3 @@
import { kbnUrlDirective } from './kbnHref';
kbnUrlDirective('kbnSrc');

View file

@ -38,11 +38,17 @@ block content
loading.removeChild(loading.lastChild);
}
function bundleFile(filename) {
var anchor = document.createElement('a');
anchor.setAttribute('href', !{JSON.stringify(bundlePath)} + '/' + filename);
return anchor.href;
}
var files = [
'/bundles/commons.style.css',
'/bundles/#{app.id}.style.css',
'/bundles/commons.bundle.js',
'/bundles/#{app.id}.bundle.js'
bundleFile('commons.style.css'),
bundleFile('#{app.id}.style.css'),
bundleFile('commons.bundle.js'),
bundleFile('#{app.id}.bundle.js')
];
(function next() {