Grafana-web visual read-only/kiosk mode for integration in enteprise app -
have requirement in project integrate grafana-web in enteprise app.
some of points are:
- don't show grafana menu (dashboards read via api , integrated in app menu)
- hide playground buttons users (even if grafana offers readonly mode, prevents saving not playing around settings/data)
- for users allow edit mode (add rows, dashboard settings, dashboard save ...)
- disable sharing users
- all these visual quirks in frontend, security level low (evil user can still bypass hidden buttons , that's ok)
- create/delete dashboards done via api triggered buttons in enteprise app
as grafana has nothing this, thinking load iframe , xss hide buttons (both ui's loaded same domain).
i understand , accept torkel , grafana team decision not have readonly mode in frontend "evil" user can query backend around it, security point of view right.
but see edge cases/projects require if visual quirk.
warning: sure got it, visual styling of grafana-web , not offer security, "evil" user can still have access everything.
so here's how implemented it:
- all users have edit priviledges in grafana (the buttons hidden frontend only)
- load grafana-web iframe
- the iframe either has mask on (one can move iframe our of view or make if transparent)
- on iframe domcontentloaded handler, register mutationobserver on iframe document catch , hide buttons added dom, using amazing mutation summary library rafael weinstein.
- hide buttons @ stage rendered, in case observer registred late (it's race condition against angular rendering)
- remove mask (move iframe visible area, make opac ...)
here's customizer code triggered domcontentloaded:
// don't forget load mutation-summary.js lib function iframeload (iframe) { // disable if want users have access playground buttons like: // add rows, edit panels, dashboard settings ... readonlymode = true; // iframe "window" var iframe_window = iframe.contentwindow; // iframe "document" under mutationobserver dom changes var iframe_document = iframe.contentdocument; var queries = [{ // main menu of grafana element: '.navbar-brand-btn' },{ // dashboard selection right of main menu element: '.navbar-page-btn' },{ // share button appearing inside .dashnav-action-icons, don't want allow // anybody, it's exposes real url, bypassing code element: 'li[ng-show="::dashboardmeta.canshare"]' },{ // dashboard delete button, under dashboard setting button element: 'a[ng-click="deletedashboard();"]' }]; if ( readonlymode ) { queries.push({ // 3 vertical dots button open row menu/edit element: '.dash-row-menu-grip' }); queries.push({ // bottom "+ add row" button element: '.add-row-panel-hint' }); queries.push({ // share button right of dashboard menu element: '.dashnav-action-icons' }); queries.push({ // "panel" menu triggered clicking panel name element: '.panel-menu' }); } var observer; observer = new mutationsummary({ callback: function (changes) { changes.foreach(function (change) { change.added.foreach(function (el) { iframe_window.angular.element(el).addclass('ng-hide'); }); }); // disconnect here free resources, on new dashboards // buttons re-rendered angular, keep block behaviour //observer.disconnect(); }, queries: queries, rootnode: iframe_document }); // hide elements if generated before registred observer // race condition afterall "angular rendering" vs "registering observer" queries.foreach( function (el) { if ( iframe_window && iframe_window.angular ) { iframe_window.angular.element(el.element).addclass('ng-hide'); } }); // remove mask or move iframe view if needed // [your code here] }
this method tested against grafana version 4.0.0-1476697633pre1 (and 1 running in play.grafana.org)
will try update code in github page, once upgrade grafana.
full example can found on github page using play.grafana.org in iframe, , play.grafana not in domain play around disable same-origin policy in chrome starting with:
google-chrome --disable-web-security --user-data-dir
Comments
Post a Comment