mirror of https://github.com/grafana/grafana
Dashboard: Migrating dashboard settings to react (#27561)
* Dashboard: Started migrating dashboard settings * Restore general settings from angular * Use react permissions component * feat(dashboard): add react LinksSettings wrapper for dash-links-editor * feat(dashboard): add react VersionsSettings wrapper for gf-dashboard-history * refactor(dashboard): replace DashboardPermissions connectWithStore with connect * chore(dashboard): folderInfo as undefined * feat(dashboard): initial commit of dashboard settings json editor * feat(dashboard): introduce save json functionality * chore(dashboard): delete obsolete imports * feat(dashboard): add save and save as buttons to settings nav * feat(dashboard): add react wrapper for annotations settings * chore(dashboard): put back canDelete for general settings delete button * Make editable * Remove makeEditable from SettingsCtrl * feat(dashboard): show json editor save button if canSave * refactor(dashboard): move hasUnsavedFolderChange to dashboard.meta * feat(dashboard): render hasUnsavedFolderChange view in permissions settings * feat(dashboard): reset hasUnsavedFolderChange on settingg save success * feat(dashboard): refresh route on sucessful settings save * test(dashboard): update snapshots * refactor(dashboard): automatically infer connected props for dashboard permissions * refactor(dashboard): give dashboard versions checkboxes some padding * Update public/app/types/folders.ts Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com> Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com>pull/29911/head
parent
0561c941af
commit
d066da42f8
@ -0,0 +1,30 @@ |
|||||||
|
import React, { PureComponent } from 'react'; |
||||||
|
import { DashboardModel } from '../../state/DashboardModel'; |
||||||
|
import { AngularComponent, getAngularLoader } from '@grafana/runtime'; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
dashboard: DashboardModel; |
||||||
|
} |
||||||
|
|
||||||
|
export class AnnotationsSettings extends PureComponent<Props> { |
||||||
|
element?: HTMLElement | null; |
||||||
|
angularCmp?: AngularComponent; |
||||||
|
|
||||||
|
componentDidMount() { |
||||||
|
const loader = getAngularLoader(); |
||||||
|
|
||||||
|
const template = '<div ng-include="\'public/app/features/annotations/partials/editor.html\'" />'; |
||||||
|
const scopeProps = { dashboard: this.props.dashboard }; |
||||||
|
this.angularCmp = loader.load(this.element, scopeProps, template); |
||||||
|
} |
||||||
|
|
||||||
|
componentWillUnmount() { |
||||||
|
if (this.angularCmp) { |
||||||
|
this.angularCmp.destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return <div ref={ref => (this.element = ref)} />; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
import React, { PureComponent } from 'react'; |
||||||
|
import { DashboardModel } from '../../state/DashboardModel'; |
||||||
|
import { AngularComponent, getAngularLoader } from '@grafana/runtime'; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
dashboard: DashboardModel; |
||||||
|
} |
||||||
|
|
||||||
|
export class GeneralSettings extends PureComponent<Props> { |
||||||
|
element?: HTMLElement | null; |
||||||
|
angularCmp?: AngularComponent; |
||||||
|
|
||||||
|
componentDidMount() { |
||||||
|
const loader = getAngularLoader(); |
||||||
|
|
||||||
|
const template = '<dashboard-settings dashboard="dashboard" />'; |
||||||
|
const scopeProps = { dashboard: this.props.dashboard }; |
||||||
|
this.angularCmp = loader.load(this.element, scopeProps, template); |
||||||
|
} |
||||||
|
|
||||||
|
componentWillUnmount() { |
||||||
|
if (this.angularCmp) { |
||||||
|
this.angularCmp.destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return <div ref={ref => (this.element = ref)} />; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer'; |
||||||
|
import { Button, CodeEditor } from '@grafana/ui'; |
||||||
|
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; |
||||||
|
import { getDashboardSrv } from '../../services/DashboardSrv'; |
||||||
|
import { DashboardModel } from '../../state/DashboardModel'; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
dashboard: DashboardModel; |
||||||
|
} |
||||||
|
|
||||||
|
export const JsonEditorSettings: React.FC<Props> = ({ dashboard }) => { |
||||||
|
const [dashboardJson, setDashboardJson] = useState<string>(JSON.stringify(dashboard.getSaveModelClone(), null, 2)); |
||||||
|
const onBlur = (value: string) => { |
||||||
|
setDashboardJson(value); |
||||||
|
}; |
||||||
|
const onClick = () => { |
||||||
|
getDashboardSrv() |
||||||
|
.saveJSONDashboard(dashboardJson) |
||||||
|
.then(() => { |
||||||
|
dashboardWatcher.reloadPage(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<h3 className="dashboard-settings__header">JSON Model</h3> |
||||||
|
<div className="dashboard-settings__subheader"> |
||||||
|
The JSON Model below is data structure that defines the dashboard. Including settings, panel settings & layout, |
||||||
|
queries etc. |
||||||
|
</div> |
||||||
|
|
||||||
|
<div> |
||||||
|
<AutoSizer disableHeight> |
||||||
|
{({ width }) => ( |
||||||
|
<CodeEditor |
||||||
|
value={dashboardJson} |
||||||
|
language="json" |
||||||
|
width={width} |
||||||
|
height="500px" |
||||||
|
showMiniMap={false} |
||||||
|
onBlur={onBlur} |
||||||
|
/> |
||||||
|
)} |
||||||
|
</AutoSizer> |
||||||
|
</div> |
||||||
|
{dashboard.meta.canSave && ( |
||||||
|
<Button className="m-t-3" onClick={onClick}> |
||||||
|
Save Changes |
||||||
|
</Button> |
||||||
|
)} |
||||||
|
</> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,30 @@ |
|||||||
|
import React, { PureComponent } from 'react'; |
||||||
|
import { DashboardModel } from '../../state/DashboardModel'; |
||||||
|
import { AngularComponent, getAngularLoader } from '@grafana/runtime'; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
dashboard: DashboardModel; |
||||||
|
} |
||||||
|
|
||||||
|
export class LinksSettings extends PureComponent<Props> { |
||||||
|
element?: HTMLElement | null; |
||||||
|
angularCmp?: AngularComponent; |
||||||
|
|
||||||
|
componentDidMount() { |
||||||
|
const loader = getAngularLoader(); |
||||||
|
|
||||||
|
const template = '<dash-links-editor dashboard="dashboard" />'; |
||||||
|
const scopeProps = { dashboard: this.props.dashboard }; |
||||||
|
this.angularCmp = loader.load(this.element, scopeProps, template); |
||||||
|
} |
||||||
|
|
||||||
|
componentWillUnmount() { |
||||||
|
if (this.angularCmp) { |
||||||
|
this.angularCmp.destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return <div ref={ref => (this.element = ref)} />; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
import React, { PureComponent } from 'react'; |
||||||
|
import { DashboardModel } from '../../state/DashboardModel'; |
||||||
|
import { AngularComponent, getAngularLoader } from '@grafana/runtime'; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
dashboard: DashboardModel; |
||||||
|
} |
||||||
|
|
||||||
|
export class VersionsSettings extends PureComponent<Props> { |
||||||
|
element?: HTMLElement | null; |
||||||
|
angularCmp?: AngularComponent; |
||||||
|
|
||||||
|
componentDidMount() { |
||||||
|
const loader = getAngularLoader(); |
||||||
|
|
||||||
|
const template = '<gf-dashboard-history dashboard="dashboard" />'; |
||||||
|
const scopeProps = { dashboard: this.props.dashboard }; |
||||||
|
this.angularCmp = loader.load(this.element, scopeProps, template); |
||||||
|
} |
||||||
|
|
||||||
|
componentWillUnmount() { |
||||||
|
if (this.angularCmp) { |
||||||
|
this.angularCmp.destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return <div ref={ref => (this.element = ref)} />; |
||||||
|
} |
||||||
|
} |
@ -1,143 +1,64 @@ |
|||||||
<aside class="dashboard-settings__aside"> |
|
||||||
<a href="{{::section.url}}" |
|
||||||
class="dashboard-settings__nav-item" |
|
||||||
ng-class="{active: ctrl.viewId === section.id}" |
|
||||||
ng-repeat="section in ctrl.sections" |
|
||||||
aria-label="{{ctrl.selectors.sectionItems(section.title)}}"> |
|
||||||
<icon name="'{{::section.icon}}'" style="margin-right: 4px;"></icon> |
|
||||||
{{::section.title}} |
|
||||||
</a> |
|
||||||
|
|
||||||
<div class="dashboard-settings__aside-actions"> |
<h3 class="dashboard-settings__header" aria-label="{{::ctrl.selectors.title}}"> |
||||||
<div ng-show="ctrl.canSave"> |
General |
||||||
<save-dashboard-button getDashboard="ctrl.getDashboard" /> |
</h3> |
||||||
</div> |
|
||||||
<div ng-show="ctrl.canSaveAs"> |
|
||||||
<save-dashboard-as-button getDashboard="ctrl.getDashboard" variant="'secondary'" /> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</aside> |
|
||||||
|
|
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'settings'"> |
|
||||||
<h3 class="dashboard-settings__header" aria-label="{{::ctrl.selectors.title}}"> |
|
||||||
General |
|
||||||
</h3> |
|
||||||
|
|
||||||
<div class="gf-form-group"> |
|
||||||
<div class="gf-form"> |
|
||||||
<label class="gf-form-label width-7">Name</label> |
|
||||||
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.title'></input> |
|
||||||
</div> |
|
||||||
<div class="gf-form"> |
|
||||||
<label class="gf-form-label width-7">Description</label> |
|
||||||
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.description'></input> |
|
||||||
</div> |
|
||||||
<div class="gf-form"> |
|
||||||
<label class="gf-form-label width-7"> |
|
||||||
Tags |
|
||||||
<info-popover mode="right-normal">Press enter to add a tag</info-popover> |
|
||||||
</label> |
|
||||||
<bootstrap-tagsinput ng-model="ctrl.dashboard.tags" tagclass="label label-tag" placeholder="add tags"> |
|
||||||
</bootstrap-tagsinput> |
|
||||||
</div> |
|
||||||
<folder-picker initial-title="ctrl.dashboard.meta.folderTitle" initial-folder-id="ctrl.dashboard.meta.folderId" |
|
||||||
on-change="ctrl.onFolderChange" enable-create-new="true" is-valid-selection="true" label-class="width-7" |
|
||||||
dashboard-id="ctrl.dashboard.id"> |
|
||||||
</folder-picker> |
|
||||||
<gf-form-switch class="gf-form" label="Editable" |
|
||||||
tooltip="Uncheck, then save and reload to disable all dashboard editing" checked="ctrl.dashboard.editable" |
|
||||||
label-class="width-7"> |
|
||||||
</gf-form-switch> |
|
||||||
</div> |
|
||||||
|
|
||||||
<time-picker-settings |
<div class="gf-form-group"> |
||||||
onTimeZoneChange="ctrl.onTimeZoneChange" |
|
||||||
onRefreshIntervalChange="ctrl.onRefreshIntervalChange" |
|
||||||
onNowDelayChange="ctrl.onNowDelayChange" |
|
||||||
onHideTimePickerChange="ctrl.onHideTimePickerChange" |
|
||||||
renderCount="ctrl.renderCount" |
|
||||||
refreshIntervals="ctrl.dashboard.timepicker.refresh_intervals" |
|
||||||
timePickerHidden="ctrl.dashboard.timepicker.hidden" |
|
||||||
nowDelay="ctrl.dashboard.timepicker.nowDelay" |
|
||||||
timezone="ctrl.dashboard.timezone" |
|
||||||
> |
|
||||||
</time-picker-settings> |
|
||||||
|
|
||||||
<h5 class="section-heading">Panel Options</h5> |
|
||||||
<div class="gf-form"> |
<div class="gf-form"> |
||||||
<label class="gf-form-label width-11"> |
<label class="gf-form-label width-7">Name</label> |
||||||
Graph Tooltip |
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.title'></input> |
||||||
<info-popover mode="right-normal"> |
|
||||||
Cycle between options using Shortcut: CTRL+O or CMD+O |
|
||||||
</info-popover> |
|
||||||
</label> |
|
||||||
<div class="gf-form-select-wrapper"> |
|
||||||
<select ng-model="ctrl.dashboard.graphTooltip" class='gf-form-input' |
|
||||||
ng-options="f.value as f.text for f in [{value: 0, text: 'Default'}, {value: 1, text: 'Shared crosshair'},{value: 2, text: 'Shared Tooltip'}]"></select> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="gf-form-button-row"> |
|
||||||
<button class="btn btn-danger" ng-click="ctrl.deleteDashboard()" ng-show="ctrl.canDelete" |
|
||||||
aria-label="Dashboard settings page delete dashboard button"> |
|
||||||
Delete Dashboard |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'annotations'" |
|
||||||
ng-include="'public/app/features/annotations/partials/editor.html'"> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'templating'"> |
|
||||||
<variable-editor-container /> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'links'"> |
|
||||||
<dash-links-editor dashboard="ctrl.dashboard"></dash-links-editor> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'versions'"> |
|
||||||
<gf-dashboard-history dashboard="dashboard"></gf-dashboard-history> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'dashboard_json'"> |
|
||||||
<h3 class="dashboard-settings__header">JSON Model</h3> |
|
||||||
<div class="dashboard-settings__subheader"> |
|
||||||
The JSON Model below is data structure that defines the dashboard. Including settings, panel settings & layout, |
|
||||||
queries etc. |
|
||||||
</div> |
</div> |
||||||
|
|
||||||
<div class="gf-form"> |
<div class="gf-form"> |
||||||
<code-editor content="ctrl.json" data-mode="json" data-max-lines=30></code-editor> |
<label class="gf-form-label width-7">Description</label> |
||||||
</div> |
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.description'></input> |
||||||
|
|
||||||
<div class="gf-form-button-row"> |
|
||||||
<button class="btn btn-primary" ng-click="ctrl.saveDashboardJson()" ng-show="ctrl.canSave"> |
|
||||||
Save Changes |
|
||||||
</button> |
|
||||||
</div> |
</div> |
||||||
</div> |
<div class="gf-form"> |
||||||
|
<label class="gf-form-label width-7"> |
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'permissions'"> |
Tags |
||||||
<dashboard-permissions ng-if="ctrl.dashboard && !ctrl.hasUnsavedFolderChange" dashboardId="ctrl.dashboard.id" |
<info-popover mode="right-normal">Press enter to add a tag</info-popover> |
||||||
backendSrv="ctrl.backendSrv" folder="ctrl.getFolder()" /> |
</label> |
||||||
<div ng-if="ctrl.hasUnsavedFolderChange"> |
<bootstrap-tagsinput ng-model="ctrl.dashboard.tags" tagclass="label label-tag" placeholder="add tags"> |
||||||
<h5>You have changed folder, please save to view permissions.</h5> |
</bootstrap-tagsinput> |
||||||
</div> |
</div> |
||||||
|
<folder-picker initial-title="ctrl.dashboard.meta.folderTitle" initial-folder-id="ctrl.dashboard.meta.folderId" |
||||||
|
on-change="ctrl.onFolderChange" enable-create-new="true" is-valid-selection="true" label-class="width-7" |
||||||
|
dashboard-id="ctrl.dashboard.id"> |
||||||
|
</folder-picker> |
||||||
|
<gf-form-switch class="gf-form" label="Editable" |
||||||
|
tooltip="Uncheck, then save and reload to disable all dashboard editing" checked="ctrl.dashboard.editable" |
||||||
|
label-class="width-7"> |
||||||
|
</gf-form-switch> |
||||||
</div> |
</div> |
||||||
|
|
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === '404'"> |
<time-picker-settings |
||||||
<h3 class="dashboard-settings__header">Settings view not found</h3> |
onTimeZoneChange="ctrl.onTimeZoneChange" |
||||||
|
onRefreshIntervalChange="ctrl.onRefreshIntervalChange" |
||||||
<div> |
onNowDelayChange="ctrl.onNowDelayChange" |
||||||
<h5>The settings page could not be found or you do not have permission to access it</h5> |
onHideTimePickerChange="ctrl.onHideTimePickerChange" |
||||||
|
renderCount="ctrl.renderCount" |
||||||
|
refreshIntervals="ctrl.dashboard.timepicker.refresh_intervals" |
||||||
|
timePickerHidden="ctrl.dashboard.timepicker.hidden" |
||||||
|
nowDelay="ctrl.dashboard.timepicker.nowDelay" |
||||||
|
timezone="ctrl.dashboard.timezone" |
||||||
|
> |
||||||
|
</time-picker-settings> |
||||||
|
|
||||||
|
<h5 class="section-heading">Panel Options</h5> |
||||||
|
<div class="gf-form"> |
||||||
|
<label class="gf-form-label width-11"> |
||||||
|
Graph Tooltip |
||||||
|
<info-popover mode="right-normal"> |
||||||
|
Cycle between options using Shortcut: CTRL+O or CMD+O |
||||||
|
</info-popover> |
||||||
|
</label> |
||||||
|
<div class="gf-form-select-wrapper"> |
||||||
|
<select ng-model="ctrl.dashboard.graphTooltip" class='gf-form-input' |
||||||
|
ng-options="f.value as f.text for f in [{value: 0, text: 'Default'}, {value: 1, text: 'Shared crosshair'},{value: 2, text: 'Shared Tooltip'}]"></select> |
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
|
<div class="gf-form-button-row"> |
||||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'make_editable'"> |
<button class="btn btn-danger" ng-click="ctrl.deleteDashboard()" ng-show="ctrl.canDelete" |
||||||
<h3 class="dashboard-settings__header">Make Editable</h3> |
aria-label="Dashboard settings page delete dashboard button"> |
||||||
|
Delete Dashboard |
||||||
<button class="btn btn-primary" ng-click="ctrl.makeEditable()"> |
|
||||||
Make Editable |
|
||||||
</button> |
</button> |
||||||
</div> |
</div> |
||||||
|
Loading…
Reference in new issue