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"> |
||||
<div ng-show="ctrl.canSave"> |
||||
<save-dashboard-button getDashboard="ctrl.getDashboard" /> |
||||
</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> |
||||
<h3 class="dashboard-settings__header" aria-label="{{::ctrl.selectors.title}}"> |
||||
General |
||||
</h3> |
||||
|
||||
<time-picker-settings |
||||
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-group"> |
||||
<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 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. |
||||
<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"> |
||||
<code-editor content="ctrl.json" data-mode="json" data-max-lines=30></code-editor> |
||||
</div> |
||||
|
||||
<div class="gf-form-button-row"> |
||||
<button class="btn btn-primary" ng-click="ctrl.saveDashboardJson()" ng-show="ctrl.canSave"> |
||||
Save Changes |
||||
</button> |
||||
<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> |
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'permissions'"> |
||||
<dashboard-permissions ng-if="ctrl.dashboard && !ctrl.hasUnsavedFolderChange" dashboardId="ctrl.dashboard.id" |
||||
backendSrv="ctrl.backendSrv" folder="ctrl.getFolder()" /> |
||||
<div ng-if="ctrl.hasUnsavedFolderChange"> |
||||
<h5>You have changed folder, please save to view permissions.</h5> |
||||
<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> |
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === '404'"> |
||||
<h3 class="dashboard-settings__header">Settings view not found</h3> |
||||
|
||||
<div> |
||||
<h5>The settings page could not be found or you do not have permission to access it</h5> |
||||
<time-picker-settings |
||||
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"> |
||||
<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 class="dashboard-settings__content" ng-if="ctrl.viewId === 'make_editable'"> |
||||
<h3 class="dashboard-settings__header">Make Editable</h3> |
||||
|
||||
<button class="btn btn-primary" ng-click="ctrl.makeEditable()"> |
||||
Make Editable |
||||
<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> |
||||
|
Loading…
Reference in new issue