diff --git a/src/plugins/kibana/index.js b/src/plugins/kibana/index.js index b1ea675fd1057..9b1d926aa9664 100644 --- a/src/plugins/kibana/index.js +++ b/src/plugins/kibana/index.js @@ -2,7 +2,7 @@ import ingest from './server/routes/api/ingest'; module.exports = function (kibana) { return new kibana.Plugin({ - + id: 'kibana', config: function (Joi) { return Joi.object({ enabled: Joi.boolean().default(true), @@ -13,7 +13,9 @@ module.exports = function (kibana) { uiExports: { app: { + id: 'kibana', title: 'Kibana', + listed: false, description: 'the kibana you know and love', //icon: 'plugins/kibana/settings/sections/about/barcode.svg', main: 'plugins/kibana/kibana', @@ -32,8 +34,39 @@ module.exports = function (kibana) { return { kbnDefaultAppId: config.get('kibana.defaultAppId') }; + }, + }, + + links: [ + { + title: 'Discover', + order: -1003, + url: '/app/kibana#/discover', + description: 'interactively explore your data', + icon: 'plugins/kibana/assets/discover.svg', + }, + { + title: 'Visualize', + order: -1002, + url: '/app/kibana#/visualize', + description: 'design data visualizations', + icon: 'plugins/kibana/assets/visualize.svg', + }, + { + title: 'Dashboard', + order: -1001, + url: '/app/kibana#/dashboard', + description: 'compose visualizations for much win', + icon: 'plugins/kibana/assets/dashboard.svg', + }, + { + title: 'Settings', + order: 1000, + url: '/app/kibana#/settings', + description: 'define index patterns, change config, and more', + icon: 'plugins/kibana/assets/settings.svg', } - } + ] }, init: function (server, options) { diff --git a/src/plugins/kibana/public/assets/dashboard.svg b/src/plugins/kibana/public/assets/dashboard.svg new file mode 100755 index 0000000000000..083918d1c51b0 --- /dev/null +++ b/src/plugins/kibana/public/assets/dashboard.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/plugins/kibana/public/assets/discover.svg b/src/plugins/kibana/public/assets/discover.svg new file mode 100755 index 0000000000000..7b923b7c8aad2 --- /dev/null +++ b/src/plugins/kibana/public/assets/discover.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/plugins/kibana/public/assets/logout.svg b/src/plugins/kibana/public/assets/logout.svg new file mode 100755 index 0000000000000..123ceceaef10e --- /dev/null +++ b/src/plugins/kibana/public/assets/logout.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/plugins/kibana/public/assets/search.svg b/src/plugins/kibana/public/assets/search.svg new file mode 100755 index 0000000000000..1e4ffb6c1b4fc --- /dev/null +++ b/src/plugins/kibana/public/assets/search.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/plugins/kibana/public/assets/settings.svg b/src/plugins/kibana/public/assets/settings.svg new file mode 100755 index 0000000000000..7bbbce4ac0874 --- /dev/null +++ b/src/plugins/kibana/public/assets/settings.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/plugins/kibana/public/assets/visualize.svg b/src/plugins/kibana/public/assets/visualize.svg new file mode 100755 index 0000000000000..a1cde5a166481 --- /dev/null +++ b/src/plugins/kibana/public/assets/visualize.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/plugins/kibana/public/kibana.js b/src/plugins/kibana/public/kibana.js index 6c0182605ae06..b7fe5cc84875a 100644 --- a/src/plugins/kibana/public/kibana.js +++ b/src/plugins/kibana/public/kibana.js @@ -28,13 +28,13 @@ routes chrome .setBrand({ - 'logo': 'url(' + kibanaLogoUrl + ') center / 160px 70px no-repeat #e8488b', - 'smallLogo': 'url(' + kibanaLogoUrl + ') center / 160px 70px no-repeat #e8488b' + 'logo': 'url(' + kibanaLogoUrl + ') 6px 10px / 140px 50px no-repeat #e8488b', + 'smallLogo': 'url(' + kibanaLogoUrl + ') 6px 10px / 140px 50px no-repeat #e8488b' }) .setNavBackground('#222222') .setTabDefaults({ resetWhenActive: true, - lastUrlStore: window.sessionStore, + lastUrlStore: window.sessionStorage, activeIndicatorColor: '#656a76' }) .setTabs([ diff --git a/src/plugins/statusPage/public/statusPage.js b/src/plugins/statusPage/public/statusPage.js index b7ce8705fc5c6..32ba83efbd756 100644 --- a/src/plugins/statusPage/public/statusPage.js +++ b/src/plugins/statusPage/public/statusPage.js @@ -54,3 +54,8 @@ const chrome = require('ui/chrome') ui.refresh(); }); + +require('ui/modules').get('kibana') +.config(function (appSwitcherEnsureNavigationProvider) { + appSwitcherEnsureNavigationProvider.forceNavigation(true); +}); diff --git a/src/ui/index.js b/src/ui/index.js index 2c3981c5fadf4..aae26b9a86a69 100644 --- a/src/ui/index.js +++ b/src/ui/index.js @@ -71,7 +71,7 @@ module.exports = async (kbnServer, server, config) => { server.decorate('reply', 'renderApp', function (app) { const payload = { app: app, - nav: uiExports.apps, + nav: uiExports.navLinks.inOrder, version: kbnServer.version, buildNum: config.get('pkg.buildNum'), buildSha: config.get('pkg.buildSha'), diff --git a/src/ui/public/chrome/Tab.js b/src/ui/public/chrome/Tab.js index 1a6884047f5ae..7cc9a108ab9ae 100644 --- a/src/ui/public/chrome/Tab.js +++ b/src/ui/public/chrome/Tab.js @@ -1,3 +1,4 @@ +import notify from 'ui/notify'; import _ from 'lodash'; import { escapeRegExp as reEsc } from 'lodash'; import { parse, format } from 'url'; @@ -28,7 +29,16 @@ export default class Tab { this.lastUrlStoreKey = `lastUrl:${this.id}`; this.lastUrlStore = spec.lastUrlStore; - this.lastUrl = this.lastUrlStore ? this.lastUrlStore.getItem(this.lastUrlStoreKey) : null; + + this.lastUrl = null; + if (this.lastUrlStore) { + this.lastUrl = this.lastUrlStore.getItem(this.lastUrlStoreKey); + if (this.lastUrl && !this.lastUrl.startsWith(this.rootUrl)) { + notify.log(`Found invalid lastUrl for tab with root url ${this.rootUrl}: "${this.lastUrl}"`); + this.lastUrl = null; + this.lastUrlStore.removeItem(this.lastUrlStoreKey); + } + } } href() { @@ -54,7 +64,8 @@ export default class Tab { let lastUrl = this.getLastUrl(); if (!lastUrl.startsWith(rootUrl)) { - throw new Error(`Tab "${id}" has invalid root "${rootUrl}" for last url "${lastUrl}"`); + notify.log(`Tab "${id}" has invalid root "${rootUrl}" for last url "${lastUrl}"`); + lastUrl = rootUrl; } return lastUrl.slice(rootUrl.length); diff --git a/src/ui/public/chrome/__tests__/Tab.js b/src/ui/public/chrome/__tests__/Tab.js index 6f4b05adc402c..2d13e2da5edcf 100644 --- a/src/ui/public/chrome/__tests__/Tab.js +++ b/src/ui/public/chrome/__tests__/Tab.js @@ -1,3 +1,4 @@ +import sinon from 'auto-release-sinon'; import Tab from '../Tab'; import expect from 'expect.js'; import TabFakeStore from './_TabFakeStore'; @@ -89,13 +90,24 @@ describe('Chrome Tab', function () { it('discovers the lastUrl', function () { const lastUrlStore = new TabFakeStore(); const tab = new Tab({ id: 'foo', lastUrlStore }); - expect(tab.lastUrl).to.not.equal('bar'); + expect(tab.lastUrl).to.not.equal('/foo/bar'); - tab.setLastUrl('bar'); - expect(tab.lastUrl).to.equal('bar'); + tab.setLastUrl('/foo/bar'); + expect(tab.lastUrl).to.equal('/foo/bar'); const tab2 = new Tab({ id: 'foo', lastUrlStore }); - expect(tab2.lastUrl).to.equal('bar'); + expect(tab2.lastUrl).to.equal('/foo/bar'); + }); + + it('logs a warning about last urls that do not match the rootUrl', function () { + const lastUrlStore = new TabFakeStore(); + const tab = new Tab({ id: 'foo', baseUrl: '/bar', lastUrlStore }); + tab.setLastUrl('/bar/foo/1'); + + const stub = sinon.stub(console, 'log'); + const tab2 = new Tab({ id: 'foo', baseUrl: '/baz', lastUrlStore }); + sinon.assert.calledOnce(stub); + expect(tab2.lastUrl).to.equal(null); }); }); @@ -167,14 +179,14 @@ describe('Chrome Tab', function () { expect(tab.getLastPath()).to.equal('/index'); }); - it('throws an error if the lastUrl does not extend the root url', function () { - expect(function () { - const baseUrl = 'http://local:5601/app/visualize#'; - const tab = new Tab({ baseUrl }); + it('logs a warning if the lastUrl does not extend the root url', function () { + const baseUrl = 'http://local:5601/app/visualize#'; + const tab = new Tab({ baseUrl }); + sinon.stub(console, 'log'); - tab.setLastUrl('http://local:5601/'); - tab.getLastPath(); - }).to.throwError(/invalid.*root/); + tab.setLastUrl('http://local:5601/'); + tab.getLastPath(); + sinon.assert.calledOnce(console.log);// eslint-disable-line no-console }); }); diff --git a/src/ui/public/chrome/__tests__/_TabFakeStore.js b/src/ui/public/chrome/__tests__/_TabFakeStore.js index 1b8ab81602259..a678440d14b78 100644 --- a/src/ui/public/chrome/__tests__/_TabFakeStore.js +++ b/src/ui/public/chrome/__tests__/_TabFakeStore.js @@ -4,6 +4,7 @@ export default class TabFakeStore { constructor() { this[store] = new Map(); } getItem(k) { return this[store].get(k); } setItem(k, v) { return this[store].set(k, v); } + removeItem(k) { return this[store].delete(k); } getKeys() { return [ ...this[store].keys() ]; } getValues() { return [ ...this[store].values() ]; } } diff --git a/src/ui/public/chrome/api/__tests__/apps.js b/src/ui/public/chrome/api/__tests__/apps.js index 059cc2a40d9ab..498dec79c5981 100644 --- a/src/ui/public/chrome/api/__tests__/apps.js +++ b/src/ui/public/chrome/api/__tests__/apps.js @@ -86,11 +86,11 @@ describe('Chrome API :: apps', function () { describe('#getAppUrl()', function () { it('returns the resolved url of the current app', function () { const chrome = {}; - const app = { url: '/foo' }; + const app = { navLink: { url: '/foo' } }; setup(chrome, { app }); const a = document.createElement('a'); - a.setAttribute('href', app.url); + a.setAttribute('href', app.navLink.url); expect(chrome.getAppUrl()).to.equal(a.href); }); diff --git a/src/ui/public/chrome/api/apps.js b/src/ui/public/chrome/api/apps.js index ee6b7b1b86aff..c94dd537c28c7 100644 --- a/src/ui/public/chrome/api/apps.js +++ b/src/ui/public/chrome/api/apps.js @@ -3,8 +3,8 @@ import { resolve } from 'url'; module.exports = function (chrome, internals) { - if (internals.app) { - internals.app.url = resolve(window.location.href, internals.app.url); + if (get(internals, 'app.navLink.url')) { + internals.app.navLink.url = resolve(window.location.href, internals.app.navLink.url); } internals.appUrlStore = internals.appUrlStore || window.sessionStorage; @@ -35,7 +35,7 @@ module.exports = function (chrome, internals) { }; chrome.getAppUrl = function () { - return get(internals, ['app', 'url']); + return get(internals, ['app', 'navLink', 'url']); }; chrome.getInjected = function (name, def) { diff --git a/src/ui/public/chrome/api/nav.js b/src/ui/public/chrome/api/nav.js index 297df1543be3a..0e100ba1119c0 100644 --- a/src/ui/public/chrome/api/nav.js +++ b/src/ui/public/chrome/api/nav.js @@ -1,15 +1,11 @@ import { parse, format } from 'url'; -import { startsWith, isString } from 'lodash'; +import { startsWith, isString, find } from 'lodash'; export default function (chrome, internals) { chrome.getNavLinks = function () { return internals.nav; }; - chrome.getLastSubUrlFor = function (url) { - return internals.appUrlStore.getItem(`lastSubUrl:${url}`); - }; - chrome.getBasePath = function () { return internals.basePath || ''; }; @@ -34,26 +30,46 @@ export default function (chrome, internals) { }); }; + function lastSubUrlKey(link) { + return `lastSubUrl:${link.url}`; + } + + function setLastUrl(link, url) { + link.lastSubUrl = url; + internals.appUrlStore.setItem(lastSubUrlKey(link), url); + } + + function refreshLastUrl(link) { + link.lastSubUrl = internals.appUrlStore.getItem(lastSubUrlKey(link)); + } + internals.trackPossibleSubUrl = function (url) { for (const link of internals.nav) { - if (startsWith(url, link.url)) { - link.lastSubUrl = url; - internals.appUrlStore.setItem(`lastSubUrl:${link.url}`, url); + link.active = startsWith(url, link.url); + + if (link.active) { + setLastUrl(link, url); + continue; } + + const matchingTab = find(internals.tabs, { rootUrl: link.url }); + if (matchingTab) { + setLastUrl(link, matchingTab.getLastUrl()); + continue; + } + + refreshLastUrl(link); } }; internals.nav.forEach(link => { // convert all link urls to absolute urls - var a = document.createElement('a'); a.setAttribute('href', link.url); link.url = a.href; - link.lastSubUrl = chrome.getLastSubUrlFor(link.url); - - if (link.url === chrome.getAppUrl()) { - link.active = true; - } }); + // simulate a possible change in url to initialize the + // link.active and link.lastUrl properties + internals.trackPossibleSubUrl(document.location.href); }; diff --git a/src/ui/public/chrome/chrome.html b/src/ui/public/chrome/chrome.html index f3c9ff4e06682..a1a2364f80be1 100644 --- a/src/ui/public/chrome/chrome.html +++ b/src/ui/public/chrome/chrome.html @@ -43,51 +43,53 @@
- - - - + + -
+
+
diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.html b/src/ui/public/chrome/directives/app_switcher/app_switcher.html index 7613cb03142b4..382e35b8a4023 100644 --- a/src/ui/public/chrome/directives/app_switcher/app_switcher.html +++ b/src/ui/public/chrome/directives/app_switcher/app_switcher.html @@ -1,7 +1,7 @@