Disable View, Share and More menu from graph

Hi - short question; did you replace the complete content within “return” section? or did you add your code above?
regards Peter

or even better - would you mind to share your HeaderPanel.tsx?

regards Peter

Sure. Here is the complete file.

Grtz. Ricardo

import React, { FC } from 'react';
import { css, cx } from '@emotion/css';
import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data';
import { Icon, useStyles2 } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';

import PanelHeaderCorner from './PanelHeaderCorner';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
import { PanelHeaderNotices } from './PanelHeaderNotices';
import { PanelHeaderMenuTrigger } from './PanelHeaderMenuTrigger';
import { PanelHeaderLoadingIndicator } from './PanelHeaderLoadingIndicator';
import { PanelHeaderMenuWrapper } from './PanelHeaderMenuWrapper';

import config from 'app/core/config';

export interface Props {
  panel: PanelModel;
  dashboard: DashboardModel;
  title?: string;
  description?: string;
  links?: DataLink[];
  error?: string;
  alertState?: string;
  isViewing: boolean;
  isEditing: boolean;
  data: PanelData;
}

export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, data, alertState, dashboard }) => {
  const onCancelQuery = () => panel.getQueryRunner().cancelQuery();
  const title = panel.getDisplayTitle();
  const className = cx('panel-header', !(isViewing || isEditing) ? 'grid-drag-handle' : '');
  const styles = useStyles2(panelStyles);
  const isAdmin = config.bootData.user.isGrafanaAdmin;

  return (
    <>
      <PanelHeaderLoadingIndicator state={data.state} onClick={onCancelQuery} />
      <PanelHeaderCorner
        panel={panel}
        title={panel.title}
        description={panel.description}
        scopedVars={panel.scopedVars}
        links={getPanelLinksSupplier(panel)}
        error={error}
      />
      <div className={className}>
        <PanelHeaderMenuTrigger data-testid={selectors.components.Panels.Panel.title(title)}>
          {({ closeMenu, panelMenuOpen }) => {
            return (
              <div className="panel-title">
                <PanelHeaderNotices frames={data.series} panelId={panel.id} />
                {alertState ? (
                  <Icon
                    name={alertState === 'alerting' ? 'heart-break' : 'heart'}
                    className="icon-gf panel-alert-icon"
                    style={{ marginRight: '4px' }}
                    size="sm"
                  />
                ) : null}
                <h2 className={styles.titleText}>{title}</h2>
                {(() => {
                  if (isAdmin) {
                    return (
                      <span>
                        <Icon name="angle-down" className="panel-menu-toggle" />
                        <PanelHeaderMenuWrapper
                          panel={panel}
                          dashboard={dashboard}
                          show={panelMenuOpen}
                          onClose={closeMenu}
                        />
                        {data.request && data.request.timeInfo && (
                          <span className="panel-time-info">
                            <Icon name="clock-nine" size="sm" /> {data.request.timeInfo}
                          </span>
                        )}
                      </span>
                    );
                  } else {
                    return (
                      <span className={styles.isView}>
                        <Icon name="angle-down" className="panel-menu-toggle" />
                        <PanelHeaderMenuWrapper
                          panel={panel}
                          dashboard={dashboard}
                          show={panelMenuOpen}
                          onClose={closeMenu}
                        />
                        {data.request && data.request.timeInfo && (
                          <span className="panel-time-info">
                            <Icon name="clock-nine" size="sm" /> {data.request.timeInfo}
                          </span>
                        )}
                      </span>
                    );
                  }
                })()}
              </div>
            );
          }}
        </PanelHeaderMenuTrigger>
      </div>
    </>
  );
};

const panelStyles = (theme: GrafanaTheme2) => {
  return {
    titleText: css`
      text-overflow: ellipsis;
      overflow: hidden;
      white-space: nowrap;
      max-width: calc(100% - 38px);
      cursor: pointer;
      font-weight: ${theme.typography.fontWeightMedium};
      font-size: ${theme.typography.body.fontSize};
      margin: 0;

      &:hover {
        color: ${theme.colors.text.primary};
      }
      .panel-has-alert & {
        max-width: calc(100% - 54px);
      }
    `,
    isView: css`
      display: none;
    `,
  };
};

cool tx - does it work with current version as well?

regards Peter

running 9.0.7 logging in as guest with view only access - but still can see the top menu to export dashboard/view json

regards Peter

You can disable the top nav by adding &kiosk to the URL. We use the Grafana dashboards in an iframe. My solution is only for the dropdown/nav in the panels.

1 Like

okay got it - tx

Peter

@anon91959451

What is the best way to answer the question “did you manage to hide the menu?”? I am trying to do the same."

I would appreciate it if you could elaborate on the steps to accomplish this.

Any update on this? It would be very useful in a kiosk LCD screen where button space is very tight and general public unhelpfully press inspect and download.

I recently enabled Grafana panels in iframe on portal, I did following changes in backend, as after lot of effort I realized that front-end fixes are not possible due to CORS issues.

  • Removed drop-down panel for non-admin
  • Removed all the key bindings as that would be dangerous in iframe
  • If some edit is done in iframe (mainly legend specific), then any parallel dashboard update will be ignored, and loaded on page reload only. Anyways viewer can not save anything.
  • JWT token will expire, so reloading page after set interval.
  • Also for security, I redirect call to azure function, from there do keycloak validation based on portal client state parameters and then asking for redirect with proper url.
  • In reverse proxy, allow only kiosk call (you need to enable api and public urls too), that way you will be sure that no one can move out of kiosk mode, even if url is replayed.

Please find patch below for Grafana version 9.2.3 -
diff --git a/public/app/core/components/AppChrome/AppChromeService.tsx b/public/app/core/components/AppChrome/AppChromeService.tsx
index 54efe9e132…2571676c18 100644
— a/public/app/core/components/AppChrome/AppChromeService.tsx
+++ b/public/app/core/components/AppChrome/AppChromeService.tsx
@@ -93,8 +93,8 @@ export class AppChromeService {
};

exitKioskMode() {

  • this.update({ kioskMode: undefined });
  • locationService.partial({ kiosk: null });

setKioskModeFromUrl(kiosk: UrlQueryValue) {
diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts
index 6a4e009372…c3f99cd330 100644
— a/public/app/core/services/backend_srv.ts
+++ b/public/app/core/services/backend_srv.ts
@@ -23,7 +23,7 @@ import { DashboardSearchItem } from ‘app/features/search/types’;
import { getGrafanaStorage } from ‘app/features/storage/storage’;
import { TokenRevokedModal } from ‘app/features/users/TokenRevokedModal’;
import { DashboardDTO, FolderDTO } from ‘app/types’;

+import { locationService } from ‘@grafana/runtime’;
import { ShowModalReactEvent } from ‘…/…/types/events’;
import {
isContentTypeApplicationJson,
@@ -305,6 +305,7 @@ export class BackendSrv implements BackendService {
}

showErrorAlert(config: BackendSrvRequest, err: FetchError) {
+
if (config.showErrorAlert === false) {
return;
}
@@ -361,7 +362,7 @@ export class BackendSrv implements BackendService {
if (!err.isHandled) {
this.showErrorAlert(options, err);
}

  •  }, 50);
    
  •  }, 20);
    

    }

    this.inspectorStream.next(err);
    diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts
    index 9706221a0e…61518a9feb 100644
    — a/public/app/core/services/keybindingSrv.ts
    +++ b/public/app/core/services/keybindingSrv.ts
    @@ -23,16 +23,19 @@ import {
    import { AppChromeService } from ‘…/components/AppChrome/AppChromeService’;
    import { HelpModal } from ‘…/components/help/HelpModal’;
    import { contextSrv } from ‘…/core’;

+import { config as adminconfig } from ‘app/core/config’;
import { toggleTheme } from ‘./toggleTheme’;
import { withFocusedPanel } from ‘./withFocusedPanelId’;

export class KeybindingSrv {
constructor(private locationService: LocationService, private chromeService: AppChromeService) {}

clearAndInitGlobalBindings() {
Mousetrap.reset();

  • const isAdmin = adminconfig.bootData.user.isGrafanaAdmin;

  • if (isAdmin) {
    if (this.locationService.getLocation().pathname !== ‘/login’) {
    this.bind([‘?’, ‘h’], this.showHelpModal);
    this.bind(‘g h’, this.goToHome);
    @@ -52,6 +55,7 @@ export class KeybindingSrv {
    this.bind(‘t n’, () => this.toggleNav());
    }
    }

  • }

    bindGlobalEsc() {
    this.bindGlobal(‘esc’, this.globalEsc);
    @@ -193,6 +197,8 @@ export class KeybindingSrv {
    }

    setupTimeRangeBindings(updateUrl = true) {

  • const isAdmin = adminconfig.bootData.user.isGrafanaAdmin;

  • if(isAdmin){
    this.bind(‘t z’, () => {
    appEvents.publish(new ZoomOutEvent({ scale: 2, updateUrl }));
    });
    @@ -208,9 +214,12 @@ export class KeybindingSrv {
    this.bind(‘t right’, () => {
    appEvents.publish(new ShiftTimeEvent({ direction: ShiftTimeEventDirection.Right, updateUrl }));
    });

  • }
    }

setupDashboardBindings(dashboard: DashboardModel) {

  • const isAdmin = adminconfig.bootData.user.isGrafanaAdmin;
  • if(isAdmin){
    this.bind(‘mod+o’, () => {
    dashboard.graphTooltip = (dashboard.graphTooltip + 1) % 3;
    dashboard.events.publish(new LegacyGraphHoverClearEvent());
    @@ -350,4 +359,5 @@ export class KeybindingSrv {
    window.location.href = window.location.href + newUrlParam;
    });
    }
  • }
    }
    diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx
    index c5bf73cf32…1ad9fa6dfc 100644
    — a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx
    +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx
    @@ -14,6 +14,8 @@ import { PanelHeaderMenuTrigger } from ‘./PanelHeaderMenuTrigger’;
    import { PanelHeaderMenuWrapper } from ‘./PanelHeaderMenuWrapper’;
    import { PanelHeaderNotices } from ‘./PanelHeaderNotices’;

+import config from ‘app/core/config’;
+
export interface Props {
panel: PanelModel;
dashboard: DashboardModel;
@@ -32,6 +34,7 @@ export const PanelHeader: FC = ({ panel, error, isViewing, isEditing, dat
const title = panel.getDisplayTitle();
const className = cx(‘panel-header’, !(isViewing || isEditing) ? ‘grid-drag-handle’ : ‘’);
const styles = useStyles2(panelStyles);

  • const isAdmin = config.bootData.user.isGrafanaAdmin;

    return (
    <>
    @@ -60,7 +63,7 @@ export const PanelHeader: FC = ({ panel, error, isViewing, isEditing, dat
    />
    ) : null}

    {title}

  •              {!dashboard.meta.publicDashboardAccessToken && (
    
  •              {!dashboard.meta.publicDashboardAccessToken && isAdmin && (
                   <div data-testid="panel-dropdown">
                     <Icon name="angle-down" className="panel-menu-toggle" />
                     {panelMenuOpen ? (
    

diff --git a/public/app/features/live/dashboard/dashboardWatcher.ts b/public/app/features/live/dashboard/dashboardWatcher.ts
index e7cccd2b72…2199a4c3c7 100644
— a/public/app/features/live/dashboard/dashboardWatcher.ts
+++ b/public/app/features/live/dashboard/dashboardWatcher.ts
@@ -19,6 +19,8 @@ import { getDashboardSrv } from ‘…/…/dashboard/services/DashboardSrv’;
import { DashboardChangedModal } from ‘./DashboardChangedModal’;
import { DashboardEvent, DashboardEventAction } from ‘./types’;

+import config from ‘app/core/config’;
+
class DashboardWatcher {
channel?: LiveChannelAddress; // path to the channel
uid?: string;
@@ -121,9 +123,10 @@ class DashboardWatcher {
}

         const showPopup = this.editing || dash.hasUnsavedChanges();
  •        const isAdmin = config.bootData.user.isGrafanaAdmin;
           if (action === DashboardEventAction.Saved) {
    
  •          if (showPopup) {
    
  •          if (showPopup && isAdmin) {
               appEvents.publish(
                 new ShowModalReactEvent({
                   component: DashboardChangedModal,
    

@@ -131,10 +134,12 @@ class DashboardWatcher {
})
);
} else {

  •            appEvents.emit(AppEvents.alertSuccess, ['Dashboard updated']);
    
  •            if(isAdmin){
    
  •                appEvents.emit(AppEvents.alertSuccess, ['Dashboard updated']);
    
  •            }
               this.reloadPage();
             }
    
  •        } else if (showPopup) {
    
  •        } else if (showPopup && isAdmin) {
             if (action === DashboardEventAction.EditingStarted && !this.hasSeenNotice) {
               const editingEvent = event.message;
               const recent = this.getRecentEditingEvent();
    

Hi, could you please upload somewhere the patch? The post is messed up due to html formatting.

Thanks for your effort!

1 Like
diff --git a/public/app/core/components/AppChrome/AppChromeService.tsx b/public/app/core/components/AppChrome/AppChromeService.tsx
index 54efe9e132..2571676c18 100644
--- a/public/app/core/components/AppChrome/AppChromeService.tsx
+++ b/public/app/core/components/AppChrome/AppChromeService.tsx
@@ -93,8 +93,8 @@ export class AppChromeService {
   };
 
   exitKioskMode() {
-    this.update({ kioskMode: undefined });
-    locationService.partial({ kiosk: null });
+    //this.update({ kioskMode: undefined });
+    //locationService.partial({ kiosk: null });
   }
 
   setKioskModeFromUrl(kiosk: UrlQueryValue) {
diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts
index 6a4e009372..c3f99cd330 100644
--- a/public/app/core/services/backend_srv.ts
+++ b/public/app/core/services/backend_srv.ts
@@ -23,7 +23,7 @@ import { DashboardSearchItem } from 'app/features/search/types';
 import { getGrafanaStorage } from 'app/features/storage/storage';
 import { TokenRevokedModal } from 'app/features/users/TokenRevokedModal';
 import { DashboardDTO, FolderDTO } from 'app/types';
-
+import { locationService } from '@grafana/runtime';
 import { ShowModalReactEvent } from '../../types/events';
 import {
   isContentTypeApplicationJson,
@@ -305,6 +305,7 @@ export class BackendSrv implements BackendService {
   }
 
   showErrorAlert<T>(config: BackendSrvRequest, err: FetchError) {
+
     if (config.showErrorAlert === false) {
       return;
     }
@@ -361,7 +362,7 @@ export class BackendSrv implements BackendService {
         if (!err.isHandled) {
           this.showErrorAlert(options, err);
         }
-      }, 50);
+      }, 20);
     }
 
     this.inspectorStream.next(err);
diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts
index 9706221a0e..61518a9feb 100644
--- a/public/app/core/services/keybindingSrv.ts
+++ b/public/app/core/services/keybindingSrv.ts
@@ -23,16 +23,19 @@ import {
 import { AppChromeService } from '../components/AppChrome/AppChromeService';
 import { HelpModal } from '../components/help/HelpModal';
 import { contextSrv } from '../core';
-
+import { config as adminconfig } from 'app/core/config';
 import { toggleTheme } from './toggleTheme';
 import { withFocusedPanel } from './withFocusedPanelId';
 
+
 export class KeybindingSrv {
   constructor(private locationService: LocationService, private chromeService: AppChromeService) {}
 
   clearAndInitGlobalBindings() {
     Mousetrap.reset();
 
+    const isAdmin = adminconfig.bootData.user.isGrafanaAdmin;
+    if (isAdmin) {
     if (this.locationService.getLocation().pathname !== '/login') {
       this.bind(['?', 'h'], this.showHelpModal);
       this.bind('g h', this.goToHome);
@@ -52,6 +55,7 @@ export class KeybindingSrv {
       this.bind('t n', () => this.toggleNav());
     }
   }
+  }
 
   bindGlobalEsc() {
     this.bindGlobal('esc', this.globalEsc);
@@ -193,6 +197,8 @@ export class KeybindingSrv {
   }
 
   setupTimeRangeBindings(updateUrl = true) {
+   const isAdmin = adminconfig.bootData.user.isGrafanaAdmin;
+   if(isAdmin){
     this.bind('t z', () => {
       appEvents.publish(new ZoomOutEvent({ scale: 2, updateUrl }));
     });
@@ -208,9 +214,12 @@ export class KeybindingSrv {
     this.bind('t right', () => {
       appEvents.publish(new ShiftTimeEvent({ direction: ShiftTimeEventDirection.Right, updateUrl }));
     });
+   }
   }
 
   setupDashboardBindings(dashboard: DashboardModel) {
+   const isAdmin = adminconfig.bootData.user.isGrafanaAdmin;
+   if(isAdmin){
     this.bind('mod+o', () => {
       dashboard.graphTooltip = (dashboard.graphTooltip + 1) % 3;
       dashboard.events.publish(new LegacyGraphHoverClearEvent());
@@ -350,4 +359,5 @@ export class KeybindingSrv {
       window.location.href = window.location.href + newUrlParam;
     });
   }
+ }
 }
diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx
index c5bf73cf32..1ad9fa6dfc 100644
--- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx
+++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx
@@ -14,6 +14,8 @@ import { PanelHeaderMenuTrigger } from './PanelHeaderMenuTrigger';
 import { PanelHeaderMenuWrapper } from './PanelHeaderMenuWrapper';
 import { PanelHeaderNotices } from './PanelHeaderNotices';
 
+import config from 'app/core/config';
+
 export interface Props {
   panel: PanelModel;
   dashboard: DashboardModel;
@@ -32,6 +34,7 @@ export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, dat
   const title = panel.getDisplayTitle();
   const className = cx('panel-header', !(isViewing || isEditing) ? 'grid-drag-handle' : '');
   const styles = useStyles2(panelStyles);
+  const isAdmin = config.bootData.user.isGrafanaAdmin;
 
   return (
     <>
@@ -60,7 +63,7 @@ export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, dat
                     />
                   ) : null}
                   <h2 className={styles.titleText}>{title}</h2>
-                  {!dashboard.meta.publicDashboardAccessToken && (
+                  {!dashboard.meta.publicDashboardAccessToken && isAdmin && (
                     <div data-testid="panel-dropdown">
                       <Icon name="angle-down" className="panel-menu-toggle" />
                       {panelMenuOpen ? (
diff --git a/public/app/features/live/dashboard/dashboardWatcher.ts b/public/app/features/live/dashboard/dashboardWatcher.ts
index e7cccd2b72..2199a4c3c7 100644
--- a/public/app/features/live/dashboard/dashboardWatcher.ts
+++ b/public/app/features/live/dashboard/dashboardWatcher.ts
@@ -19,6 +19,8 @@ import { getDashboardSrv } from '../../dashboard/services/DashboardSrv';
 import { DashboardChangedModal } from './DashboardChangedModal';
 import { DashboardEvent, DashboardEventAction } from './types';
 
+import config from 'app/core/config';
+
 class DashboardWatcher {
   channel?: LiveChannelAddress; // path to the channel
   uid?: string;
@@ -121,9 +123,10 @@ class DashboardWatcher {
             }
 
             const showPopup = this.editing || dash.hasUnsavedChanges();
-
+	   
+            const isAdmin = config.bootData.user.isGrafanaAdmin;
             if (action === DashboardEventAction.Saved) {
-              if (showPopup) {
+              if (showPopup && isAdmin) {
                 appEvents.publish(
                   new ShowModalReactEvent({
                     component: DashboardChangedModal,
@@ -131,10 +134,12 @@ class DashboardWatcher {
                   })
                 );
               } else {
-                appEvents.emit(AppEvents.alertSuccess, ['Dashboard updated']);
+                if(isAdmin){
+                    appEvents.emit(AppEvents.alertSuccess, ['Dashboard updated']);
+                }
                 this.reloadPage();
               }
-            } else if (showPopup) {
+            } else if (showPopup && isAdmin) {
               if (action === DashboardEventAction.EditingStarted && !this.hasSeenNotice) {
                 const editingEvent = event.message;
                 const recent = this.getRecentEditingEvent();

Hello

Can you post your changes in a github fork of grafana so that others can git pull your patch?

Done.

Could you please help me know, at what location this file is present for linux systems.

I have modified this file at /usr/share/grafana/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx

But the changes are not reflecting for me. I have also tried to restart the server and verify the changes. Still it is not reflecting.

Could you please confirm if any other changes are needed apart from modifying the file, the way you have done.

Any pointers would be very helpful.

Thank You :slight_smile:

Hi, did you rebuild Grafana after the changes?

I use the command make run to rebuild and start Grafana.

Hope this helps.

1 Like

Thank you for the inputs.

I am using Grafana v8.4, I haven’t made any changes as per the suggestions you have shown.

Instead I have made changes to some 8962.xxxx.js file where the panel menu options were generated into a list. This file is present at below location.
/usr/share/grafana/public/build/

By commenting those, I was able to hide the unwanted options which were non critical as per the requirement.

Hope this helps further.

Thanks and Regards,
Bhushan