mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: hide playlist edit functionality from viewers and snapshots link from unauthenticated users (#28992)
* feat: hide snapshots menu item from viewers * feat(playlists): prevent viewers from creating/editing playlists * feat: prevent viewers seeing playlist nav link if no playlists * refactor(playlist): rename isViewer property to canEditPlaylists * revert(playlists): put back note if viewer and no playlists * refactor(snapshots): consider admin/editor permission in folders/dashboards for displaying menu item * feat(snapshots): only show snapshot nav item if user is signed in * fix(snapshots): revert snapshots to previous state if delete snapshot api error
This commit is contained in:
parent
2d49d3f5b2
commit
aa70a38391
@ -164,7 +164,15 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
|||||||
{Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true},
|
{Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true},
|
||||||
{Text: "Manage", Id: "manage-dashboards", Url: setting.AppSubUrl + "/dashboards", Icon: "sitemap"},
|
{Text: "Manage", Id: "manage-dashboards", Url: setting.AppSubUrl + "/dashboards", Icon: "sitemap"},
|
||||||
{Text: "Playlists", Id: "playlists", Url: setting.AppSubUrl + "/playlists", Icon: "presentation-play"},
|
{Text: "Playlists", Id: "playlists", Url: setting.AppSubUrl + "/playlists", Icon: "presentation-play"},
|
||||||
{Text: "Snapshots", Id: "snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots", Icon: "camera"},
|
}
|
||||||
|
|
||||||
|
if c.IsSignedIn {
|
||||||
|
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
|
||||||
|
Text: "Snapshots",
|
||||||
|
Id: "snapshots",
|
||||||
|
Url: setting.AppSubUrl + "/dashboard/snapshots",
|
||||||
|
Icon: "camera",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
navTree = append(navTree, &dtos.NavLink{
|
navTree = append(navTree, &dtos.NavLink{
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { FC, useState, useCallback, useEffect } from 'react';
|
import React, { FC, useState, useCallback, useEffect } from 'react';
|
||||||
import { ConfirmModal, Button, LinkButton } from '@grafana/ui';
|
import { ConfirmModal, Button, LinkButton } from '@grafana/ui';
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { noop } from 'rxjs';
|
|
||||||
import { Snapshot } from '../types';
|
import { Snapshot } from '../types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -28,11 +27,12 @@ export const SnapshotListTable: FC<Props> = ({ url }) => {
|
|||||||
|
|
||||||
const doRemoveSnapshot = useCallback(
|
const doRemoveSnapshot = useCallback(
|
||||||
async (snapshot: Snapshot) => {
|
async (snapshot: Snapshot) => {
|
||||||
setSnapshots(snapshots.filter(ss => ss.key !== snapshot.key));
|
const filteredSnapshots = snapshots.filter(ss => ss.key !== snapshot.key);
|
||||||
|
setSnapshots(filteredSnapshots);
|
||||||
await getBackendSrv()
|
await getBackendSrv()
|
||||||
.delete(`/api/snapshots/${snapshot.key}`)
|
.delete(`/api/snapshots/${snapshot.key}`)
|
||||||
.then(noop, () => {
|
.catch(() => {
|
||||||
setSnapshots(snapshots.concat(snapshot));
|
setSnapshots(snapshots);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[snapshots]
|
[snapshots]
|
||||||
@ -59,9 +59,9 @@ export const SnapshotListTable: FC<Props> = ({ url }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{snapshots.map((snapshot, key) => {
|
{snapshots.map(snapshot => {
|
||||||
return (
|
return (
|
||||||
<tr key={key}>
|
<tr key={snapshot.key}>
|
||||||
<td>
|
<td>
|
||||||
<a href={snapshot.url}>{snapshot.name}</a>
|
<a href={snapshot.url}>{snapshot.name}</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<div class="page-container page-body">
|
<div class="page-container page-body">
|
||||||
<div ng-if="ctrl.playlists.length > 0">
|
<div ng-if="ctrl.playlists.length > 0">
|
||||||
<div class="page-action-bar">
|
<div class="page-action-bar" ng-if="ctrl.canEditPlaylists">
|
||||||
<div class="page-action-bar__spacer"></div>
|
<div class="page-action-bar__spacer"></div>
|
||||||
<a class="btn btn-primary pull-right" href="playlists/create">
|
<a class="btn btn-primary pull-right" href="playlists/create">
|
||||||
New playlist
|
New playlist
|
||||||
@ -13,11 +13,12 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<th><strong>Name</strong></th>
|
<th><strong>Name</strong></th>
|
||||||
<th style="width: 100px"></th>
|
<th style="width: 100px"></th>
|
||||||
<th style="width: 78px"></th>
|
<th ng-if="ctrl.canEditPlaylists" style="width: 78px"></th>
|
||||||
</thead>
|
</thead>
|
||||||
<tr ng-repeat="playlist in ctrl.playlists">
|
<tr ng-repeat="playlist in ctrl.playlists">
|
||||||
<td class="link-td">
|
<td ng-class="{'link-td': ctrl.canEditPlaylists}">
|
||||||
<a href="playlists/edit/{{playlist.id}}">{{playlist.name}}</a>
|
<a ng-if="ctrl.canEditPlaylists" href="playlists/edit/{{playlist.id}}">{{playlist.name}}</a>
|
||||||
|
<span ng-if="!ctrl.canEditPlaylists">{{playlist.name}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="dropdown">
|
<td class="dropdown">
|
||||||
<button class="btn btn-inverse btn-small" data-toggle="dropdown">
|
<button class="btn btn-inverse btn-small" data-toggle="dropdown">
|
||||||
@ -44,7 +45,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td ng-if="ctrl.canEditPlaylists" class="text-right">
|
||||||
<a ng-click="ctrl.removePlaylist(playlist)" class="btn btn-danger btn-small">
|
<a ng-click="ctrl.removePlaylist(playlist)" class="btn btn-danger btn-small">
|
||||||
<icon name="'times'"></icon>
|
<icon name="'times'"></icon>
|
||||||
</a>
|
</a>
|
||||||
@ -53,16 +54,23 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="ctrl.playlists.length === 0">
|
<div ng-if="ctrl.playlists.length === 0">
|
||||||
<empty-list-cta
|
<div ng-if="ctrl.canEditPlaylists">
|
||||||
title="'There are no playlists created yet'"
|
<empty-list-cta
|
||||||
buttonIcon="'plus'"
|
title="'There are no playlists created yet'"
|
||||||
buttonLink="'playlists/create'"
|
buttonIcon="'plus'"
|
||||||
buttonTitle="'Create Playlist'"
|
buttonLink="'playlists/create'"
|
||||||
proTip="'You can use playlists to cycle dashboards on TVs without user control'"
|
buttonTitle="'Create Playlist'"
|
||||||
proTipLink="'http://docs.grafana.org/reference/playlist/'"
|
proTip="'You can use playlists to cycle dashboards on TVs without user control'"
|
||||||
proTipLinkTitle="'Learn more'"
|
proTipLink="'http://docs.grafana.org/reference/playlist/'"
|
||||||
proTipTarget="'_blank'" />
|
proTipLinkTitle="'Learn more'"
|
||||||
|
proTipTarget="'_blank'" />
|
||||||
|
</div>
|
||||||
|
<div class="grafana-info-box" ng-if="!ctrl.canEditPlaylists">
|
||||||
|
<h5>There are no playlists created yet</h5>
|
||||||
|
<p>Unfortunately you don't have permission to create playlists.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer />
|
<footer />
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { IScope } from 'angular';
|
import { IScope } from 'angular';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
|
import { OrgRole } from 'app/types';
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
import coreModule from '../../core/core_module';
|
import coreModule from '../../core/core_module';
|
||||||
|
import config from '../../core/config';
|
||||||
import { NavModelSrv } from 'app/core/nav_model_srv';
|
import { NavModelSrv } from 'app/core/nav_model_srv';
|
||||||
import { AppEventEmitter, CoreEvents } from 'app/types';
|
import { AppEventEmitter, CoreEvents } from 'app/types';
|
||||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||||
@ -11,10 +13,13 @@ import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
|||||||
export class PlaylistsCtrl {
|
export class PlaylistsCtrl {
|
||||||
playlists: any;
|
playlists: any;
|
||||||
navModel: any;
|
navModel: any;
|
||||||
|
canEditPlaylists: boolean;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $scope: IScope & AppEventEmitter, navModelSrv: NavModelSrv) {
|
constructor(private $scope: IScope & AppEventEmitter, navModelSrv: NavModelSrv) {
|
||||||
this.navModel = navModelSrv.getNav('dashboards', 'playlists', 0);
|
this.navModel = navModelSrv.getNav('dashboards', 'playlists', 0);
|
||||||
|
this.canEditPlaylists = config.bootData.user.orgRole !== OrgRole.Viewer;
|
||||||
|
|
||||||
promiseToDigest($scope)(
|
promiseToDigest($scope)(
|
||||||
getBackendSrv()
|
getBackendSrv()
|
||||||
.get('/api/playlists')
|
.get('/api/playlists')
|
||||||
|
Loading…
Reference in New Issue
Block a user