Modify Kibana Embeds to improve UX

Hi there,

We have some dashboards from Kibana that we are injecting in our app through iframes. We do not want to allow filters to display because we still have to do some cleanup in our indexes and also name the fields in a more "human readable" way, which will be tackled in future sprints.

We are using Kibana 8.0.1 (we will upgrade to 8.1.1 next week) and we are hosted on the Elastic Cloud. Our embeds are injected through a reverse proxy, so the domain for Kibana and the app match, so no CORS issues.

Actual behavior

In the mean time, while we use our MVP as is, we realized that embeding a dashboard with no filters causes poor UX as if any user clicks (either on purpose or by mistake) on any visualization piece of data, then some filters are applied and those cannot be removed because the filtering functionality is hidden in the iframe.

I though that then what we could do is just load the iframe with the filters on, and then hide the "add filter" button using some JS, but we have not succeeded in any case.

Desired behavior

The filtered values from clicking in visualization shows, but the "add filter" button does not.

using a promise function

We create a JS function that waits until the div element with id addFilterPopover shows up, and then we apply a .hide() or we inject a display:none; CSS property.

Sample code
console.log("[KibHack] :: Heating engines up...");
waitForElementToDisplay("#addFilterPopover",function(){cosole.log("[KibHack] :: injection happens here");},1000,5000);

function waitForElementToDisplay(selector, callback, checkFrequencyInMs, timeoutInMs) {
  var startTimeInMs = Date.now();
  (function loopSearch() {
    if (document.querySelector(selector) != null) {
      callback();
      return;
    }
    else {
      setTimeout(function () {
        if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs)
          return;
        loopSearch();
      }, checkFrequencyInMs);
    }
  })();
}

using a mutation observer

Similar to the above, but in this case we monitor the iframe (we need to set a specific ID when embedding so that we can use it), then we wait until the div element with id addFilterPopover is created and then we apply a .hide() or we inject a display:none; CSS property.

Sample code
console.log("[KibHack] :: Heating engines up...");
// select the target node
var target = document.querySelector('#kibFrame');

// create an observer instance
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation.type);
    });
	//iterate until the desired mutation is detected, then stop observing
	//observer.disconnect();
});

// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true }

// pass in the target node, as well as the observer options
observer.observe(target, config);
//
console.log ("bye bye");

The question

Has anyone tried to do something similar and succeeded? Please share some code snippets if possible too.

I understand the need for this feature, maybe it can become a regular option in a future version. Feel free to open a feature request here: Sign in to GitHub · GitHub

To fix it right now without touching Kibana itself: I think the problem with this is that document.querySelector won't find things within an iframe, you need to call the querySelector on the contentDocument of the iframe element instead: HTMLIFrameElement.contentDocument - Web APIs | MDN

1 Like

hi @flash1293, you are right with the querySelector, I stole and modified a bit all that code form stackoverflow.

I will post a ticket in GitHub as suggested for the functionality.

Also, still keep my faith, let's see if anyone as crazy as me has already attempted to do this. If I end up succeeding before anyone else replies (I hardly ever code JS and I hate JS), I will post the solution here.

I tried, I really did, this but still failed with this error:

Uncaught DOMException: Blocked a frame with origin "https://app.domain.com" from accessing a cross-origin frame.
    at findFilterToHide (https://app.domain.com/reports/embed1:248:40)
    at HTMLIFrameElement.onload (https://app.domain.com/reports/embed1:319:73)

usign this JS snippet

function findFilterToHide(){
    console.log("[KibHack] :: Heating engines up...")
    var checkFrequencyInMs = 2000;
    var timeoutInMs = 30000;
    var startTimeInMs = Date.now();
    var iframeContent = document.getElementById('kibFrame').contentWindow; //not really needed to divide this, I am just doing it for debugging purposes
    var iframeDocument = iframeContent.document; //fails due to SOP<-- STUCK HERE 
    (function loopSearch() {
    if (iframeDocument.getElementById('addFilterPopover') != null) {
      console.log("[KibHack] :: Found!"); //we apply the CSS or .hide() here!
      return;
    }
    else {
      setTimeout(function () {
        if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs)
          return;
        loopSearch();
        console.log("[KibHack] :: Searching...");
      }, checkFrequencyInMs);
    }
  })();
}

as inline script and calling the iframe as this:

<iframe id="kibFrame" onload="findFilterToHide()" src="https://stats.domain.com/s/embeds/app/dashboards#/view/9ab6ece9-8a53-4107-8d03-4ecffbd4cfef?embed=true&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3A'2022-01-01%2000%3A00%3A00'%2Cto%3A'2022-03-31%2023%3A59%3A59'))&show-time-filter=true" height="800" width="100%" frameborder="0"></iframe>

I also added the following config to kibana.yml

server.customResponseHeaders: {"x-frame-options":"allow-from https://app.domain.com"}

and validated it is working fine

curl -I https://stats.domain.com
HTTP/2 401
[...]
x-frame-options: allow-from https://app.domain.com

I also tried to add an Access-Control-Allow-Origin header but this seems to be banned in Elastic Cloud.

I know I can still inject that header in my proxy but I dont think it will change anything.

Am I just too crazy to be trying this?

Please note I am masking the domains in my examples :slight_smile:

1 Like

Oh, based on your post I assumed you would run Kibana behind a reverse proxy so it has the same origin as the surrounding page (e.g. under the path prefix /kibana). To do so you have to tell Kibana the base path (server.basePath setting), then it should work out of the box without touching the frame options header.

But there's little difference between that and injecting the header in the existing proxy configuration.

1 Like

@flash we are running Kibana behind a reverse proxy, it shares domain with the app while the app and kibana use different subdomains, i.e app.domain.com and stats.domain.com respectively (we use it to handle the login). This is why SOP is killing me. Running it "on a folder" is not something we can achieve at the moment, but it is on the plans.

Will try that and let you know :slight_smile: Thanks a lot for the help!

1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.