mirror of https://github.com/grafana/grafana
Scenes: SceneApp example in core (#64686)
* Scenes: Progress on demo app * Argh * Fixing breadcrumbs * Added spacer * Conditional scene objects * Quick and dirty test for drilldown link via panel title * Changed the toggle * Add url state syncing for most links * Add url state syncing for most links * Fix instance in url params * Quick and dirty tabs on the handler page * fixing breadcrumbs * Hide footer * Updates * new table styles * Update table cell link styles * Added search box * Scene app demo: dynamic data link (#60398) * Dynamically build data links * Add field override tests * Updates * Updated * Updates * before nesting routing * Something is working * Caching and nested routes working * Simplify model * Use app components from grafana/scenes * Make the monitoring app work with section nav * Fixing * Update scenes * Remove unused route * Updates * remove file * Update scenes version and use new features * Remove semicolon * removed unused thing * Add refresh pickers --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>pull/65035/head
parent
769b4598d7
commit
5c9898a860
@ -1,32 +0,0 @@ |
||||
// Libraries
|
||||
import React from 'react'; |
||||
|
||||
import { NavModelItem } from '@grafana/data'; |
||||
import { Page } from 'app/core/components/Page/Page'; |
||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; |
||||
|
||||
import { getSceneByTitle } from './scenes'; |
||||
|
||||
export interface Props extends GrafanaRouteComponentProps<{ name: string }> {} |
||||
|
||||
export const SceneEmbeddedPage = (props: Props) => { |
||||
const scene = getSceneByTitle(props.match.params.name); |
||||
|
||||
if (!scene) { |
||||
return <h2>Scene not found</h2>; |
||||
} |
||||
|
||||
const pageNav: NavModelItem = { |
||||
text: 'Embedded Scene', |
||||
}; |
||||
|
||||
return ( |
||||
<Page navId="scenes" pageNav={pageNav}> |
||||
<Page.Contents> |
||||
<scene.Component model={scene} /> |
||||
</Page.Contents> |
||||
</Page> |
||||
); |
||||
}; |
||||
|
||||
export default SceneEmbeddedPage; |
@ -0,0 +1,144 @@ |
||||
// Libraries
|
||||
import React, { useMemo, useState } from 'react'; |
||||
|
||||
import { |
||||
SceneCanvasText, |
||||
SceneFlexLayout, |
||||
SceneApp, |
||||
SceneAppPage, |
||||
SceneRouteMatch, |
||||
EmbeddedScene, |
||||
SceneAppPageLike, |
||||
} from '@grafana/scenes'; |
||||
import { usePageNav } from 'app/core/components/Page/usePageNav'; |
||||
import { PluginPageContext, PluginPageContextType } from 'app/features/plugins/components/PluginPageContext'; |
||||
|
||||
import { |
||||
getOverviewScene, |
||||
getHttpHandlerListScene, |
||||
getOverviewLogsScene, |
||||
getHandlerDetailsScene, |
||||
getHandlerLogsScene, |
||||
} from './scenes'; |
||||
|
||||
export function GrafanaMonitoringApp() { |
||||
const appScene = useMemo( |
||||
() => |
||||
new SceneApp({ |
||||
pages: [getMainPageScene()], |
||||
}), |
||||
[] |
||||
); |
||||
|
||||
const sectionNav = usePageNav('scenes')!; |
||||
const [pluginContext] = useState<PluginPageContextType>({ sectionNav }); |
||||
|
||||
return ( |
||||
<PluginPageContext.Provider value={pluginContext}> |
||||
<appScene.Component model={appScene} /> |
||||
</PluginPageContext.Provider> |
||||
); |
||||
} |
||||
|
||||
export function getMainPageScene() { |
||||
return new SceneAppPage({ |
||||
title: 'Grafana Monitoring', |
||||
subTitle: 'A custom app with embedded scenes to monitor your Grafana server', |
||||
url: '/scenes/grafana-monitoring', |
||||
hideFromBreadcrumbs: false, |
||||
getScene: getOverviewScene, |
||||
tabs: [ |
||||
new SceneAppPage({ |
||||
title: 'Overview', |
||||
url: '/scenes/grafana-monitoring', |
||||
getScene: getOverviewScene, |
||||
preserveUrlKeys: ['from', 'to', 'var-instance'], |
||||
}), |
||||
new SceneAppPage({ |
||||
title: 'HTTP handlers', |
||||
url: '/scenes/grafana-monitoring/handlers', |
||||
getScene: getHttpHandlerListScene, |
||||
preserveUrlKeys: ['from', 'to', 'var-instance'], |
||||
drilldowns: [ |
||||
{ |
||||
routePath: '/scenes/grafana-monitoring/handlers/:handler', |
||||
getPage: getHandlerDrilldownPage, |
||||
}, |
||||
], |
||||
}), |
||||
new SceneAppPage({ |
||||
title: 'Logs', |
||||
url: '/scenes/grafana-monitoring/logs', |
||||
getScene: getOverviewLogsScene, |
||||
preserveUrlKeys: ['from', 'to', 'var-instance'], |
||||
}), |
||||
], |
||||
}); |
||||
} |
||||
|
||||
export function getHandlerDrilldownPage( |
||||
match: SceneRouteMatch<{ handler: string; tab?: string }>, |
||||
parent: SceneAppPageLike |
||||
) { |
||||
const handler = decodeURIComponent(match.params.handler); |
||||
const baseUrl = `/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}`; |
||||
|
||||
return new SceneAppPage({ |
||||
title: handler, |
||||
subTitle: 'A grafana http handler is responsible for service a specific API request', |
||||
url: baseUrl, |
||||
getParentPage: () => parent, |
||||
getScene: () => getHandlerDetailsScene(handler), |
||||
tabs: [ |
||||
new SceneAppPage({ |
||||
title: 'Metrics', |
||||
url: baseUrl, |
||||
routePath: '/scenes/grafana-monitoring/handlers/:handler', |
||||
getScene: () => getHandlerDetailsScene(handler), |
||||
preserveUrlKeys: ['from', 'to', 'var-instance'], |
||||
}), |
||||
new SceneAppPage({ |
||||
title: 'Logs', |
||||
url: baseUrl + '/logs', |
||||
routePath: '/scenes/grafana-monitoring/handlers/:handler/logs', |
||||
getScene: () => getHandlerLogsScene(handler), |
||||
preserveUrlKeys: ['from', 'to', 'var-instance'], |
||||
drilldowns: [ |
||||
{ |
||||
routePath: '/scenes/grafana-monitoring/handlers/:handler/logs/:secondLevel', |
||||
getPage: getSecondLevelDrilldown, |
||||
}, |
||||
], |
||||
}), |
||||
], |
||||
}); |
||||
} |
||||
|
||||
export function getSecondLevelDrilldown( |
||||
match: SceneRouteMatch<{ handler: string; secondLevel: string }>, |
||||
parent: SceneAppPageLike |
||||
) { |
||||
const handler = decodeURIComponent(match.params.handler); |
||||
const secondLevel = decodeURIComponent(match.params.secondLevel); |
||||
const baseUrl = `/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}/logs/${secondLevel}`; |
||||
|
||||
return new SceneAppPage({ |
||||
title: secondLevel, |
||||
subTitle: 'Second level dynamic drilldown', |
||||
url: baseUrl, |
||||
getParentPage: () => parent, |
||||
getScene: () => { |
||||
return new EmbeddedScene({ |
||||
body: new SceneFlexLayout({ |
||||
children: [ |
||||
new SceneCanvasText({ |
||||
text: 'Drilldown: ' + secondLevel, |
||||
}), |
||||
], |
||||
}), |
||||
}); |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
export default GrafanaMonitoringApp; |
@ -0,0 +1,24 @@ |
||||
import React from 'react'; |
||||
|
||||
import { SelectableValue } from '@grafana/data'; |
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectStatePlain } from '@grafana/scenes'; |
||||
import { RadioButtonGroup } from '@grafana/ui'; |
||||
|
||||
export interface SceneRadioToggleState extends SceneObjectStatePlain { |
||||
options: Array<SelectableValue<string>>; |
||||
value: string; |
||||
onChange: (value: string) => void; |
||||
} |
||||
|
||||
export class SceneRadioToggle extends SceneObjectBase<SceneRadioToggleState> { |
||||
public onChange = (value: string) => { |
||||
this.setState({ value }); |
||||
this.state.onChange(value); |
||||
}; |
||||
|
||||
public static Component = ({ model }: SceneComponentProps<SceneRadioToggle>) => { |
||||
const { options, value } = model.useState(); |
||||
|
||||
return <RadioButtonGroup options={options} value={value} onChange={model.onChange} />; |
||||
}; |
||||
} |
@ -0,0 +1,20 @@ |
||||
import React from 'react'; |
||||
|
||||
import { SceneComponentProps, SceneObjectStatePlain, SceneObjectBase } from '@grafana/scenes'; |
||||
import { Input } from '@grafana/ui'; |
||||
|
||||
export interface SceneSearchBoxState extends SceneObjectStatePlain { |
||||
value: string; |
||||
} |
||||
|
||||
export class SceneSearchBox extends SceneObjectBase<SceneSearchBoxState> { |
||||
public onChange = (evt: React.FormEvent<HTMLInputElement>) => { |
||||
this.setState({ value: evt.currentTarget.value }); |
||||
}; |
||||
|
||||
public static Component = ({ model }: SceneComponentProps<SceneSearchBox>) => { |
||||
const { value } = model.useState(); |
||||
|
||||
return <Input width={25} placeholder="Search..." value={value} onChange={model.onChange} />; |
||||
}; |
||||
} |
@ -0,0 +1,536 @@ |
||||
import { FieldColorModeId, getFrameDisplayName } from '@grafana/data'; |
||||
import { locationService } from '@grafana/runtime'; |
||||
import { |
||||
SceneFlexLayout, |
||||
SceneByFrameRepeater, |
||||
SceneTimePicker, |
||||
VizPanel, |
||||
EmbeddedScene, |
||||
SceneDataNode, |
||||
SceneTimeRange, |
||||
VariableValueSelectors, |
||||
SceneQueryRunner, |
||||
SceneVariableSet, |
||||
QueryVariable, |
||||
SceneControlsSpacer, |
||||
SceneDataTransformer, |
||||
SceneRefreshPicker, |
||||
} from '@grafana/scenes'; |
||||
import { PromQuery } from 'app/plugins/datasource/prometheus/types'; |
||||
|
||||
import { SceneRadioToggle } from './SceneRadioToggle'; |
||||
import { SceneSearchBox } from './SceneSearchBox'; |
||||
import { getTableFilterTransform, getTimeSeriesFilterTransform } from './transforms'; |
||||
import { getLinkUrlWithAppUrlState } from './utils'; |
||||
|
||||
export function getHttpHandlerListScene(): EmbeddedScene { |
||||
const searchBox = new SceneSearchBox({ value: '' }); |
||||
|
||||
const httpHandlerQueries = getInstantQuery({ |
||||
expr: 'sort_desc(avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum[$__rate_interval]) * 1e3)) ', |
||||
}); |
||||
|
||||
const httpHandlerQueriesFiltered = new SceneDataTransformer({ |
||||
$data: httpHandlerQueries, |
||||
transformations: [getTableFilterTransform('')], |
||||
}); |
||||
|
||||
httpHandlerQueriesFiltered.addActivationHandler(() => { |
||||
const sub = searchBox.subscribeToState((state) => { |
||||
// Update transform and re-process them
|
||||
httpHandlerQueriesFiltered.setState({ transformations: [getTableFilterTransform(state.value)] }); |
||||
httpHandlerQueriesFiltered.reprocessTransformations(); |
||||
}); |
||||
|
||||
return () => sub.unsubscribe(); |
||||
}); |
||||
|
||||
const httpHandlersTable = new VizPanel({ |
||||
$data: httpHandlerQueriesFiltered, |
||||
pluginId: 'table', |
||||
title: '', |
||||
options: { |
||||
footer: { |
||||
enablePagination: true, |
||||
}, |
||||
}, |
||||
fieldConfig: { |
||||
defaults: {}, |
||||
overrides: [ |
||||
{ |
||||
matcher: { |
||||
id: 'byRegexp', |
||||
options: '.*', |
||||
}, |
||||
properties: [{ id: 'filterable', value: false }], |
||||
}, |
||||
{ |
||||
matcher: { |
||||
id: 'byName', |
||||
options: 'Time', |
||||
}, |
||||
properties: [{ id: 'custom.hidden', value: true }], |
||||
}, |
||||
{ |
||||
matcher: { |
||||
id: 'byName', |
||||
options: 'Value', |
||||
}, |
||||
properties: [{ id: 'displayName', value: 'Duration (Avg)' }], |
||||
}, |
||||
{ |
||||
matcher: { |
||||
id: 'byName', |
||||
options: 'handler', |
||||
}, |
||||
properties: [ |
||||
{ |
||||
id: 'links', |
||||
value: [ |
||||
{ |
||||
title: 'Go to handler drilldown view', |
||||
onBuildUrl: () => { |
||||
const params = locationService.getSearchObject(); |
||||
return getLinkUrlWithAppUrlState( |
||||
'/scenes/grafana-monitoring/handlers/${__value.text:percentencode}', |
||||
params |
||||
); |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
}); |
||||
|
||||
const reqDurationTimeSeries = new SceneQueryRunner({ |
||||
datasource: { uid: 'gdev-prometheus' }, |
||||
queries: [ |
||||
{ |
||||
refId: 'A', |
||||
//expr: ``,
|
||||
expr: 'topk(20, avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum[$__rate_interval])) * 1e3)', |
||||
range: true, |
||||
format: 'time_series', |
||||
legendFormat: '{{method}} {{handler}} (status = {{status_code}})', |
||||
maxDataPoints: 500, |
||||
}, |
||||
], |
||||
}); |
||||
|
||||
const reqDurationTimeSeriesFiltered = new SceneDataTransformer({ |
||||
$data: reqDurationTimeSeries, |
||||
transformations: [getTimeSeriesFilterTransform('')], |
||||
}); |
||||
|
||||
reqDurationTimeSeriesFiltered.addActivationHandler(() => { |
||||
const sub = searchBox.subscribeToState((state) => { |
||||
// Update transform and re-process them
|
||||
reqDurationTimeSeriesFiltered.setState({ transformations: [getTimeSeriesFilterTransform(state.value)] }); |
||||
reqDurationTimeSeriesFiltered.reprocessTransformations(); |
||||
}); |
||||
|
||||
return () => sub.unsubscribe(); |
||||
}); |
||||
|
||||
const graphsScene = new SceneByFrameRepeater({ |
||||
$data: reqDurationTimeSeriesFiltered, |
||||
body: new SceneFlexLayout({ |
||||
direction: 'column', |
||||
children: [], |
||||
}), |
||||
getLayoutChild: (data, frame, frameIndex) => { |
||||
return new SceneFlexLayout({ |
||||
key: `panel-${frameIndex}`, |
||||
direction: 'row', |
||||
placement: { minHeight: 200 }, |
||||
$data: new SceneDataNode({ |
||||
data: { |
||||
...data, |
||||
series: [frame], |
||||
}, |
||||
}), |
||||
children: [ |
||||
new VizPanel({ |
||||
pluginId: 'timeseries', |
||||
// titleLink: {
|
||||
// path: `/scenes/grafana-monitoring/handlers/${encodeURIComponent(frame.fields[1].labels.handler)}`,
|
||||
// queryKeys: ['from', 'to', 'var-instance'],
|
||||
// },
|
||||
title: getFrameDisplayName(frame), |
||||
options: { |
||||
legend: { displayMode: 'hidden' }, |
||||
}, |
||||
}), |
||||
new VizPanel({ |
||||
placement: { width: 200 }, |
||||
title: 'Last', |
||||
pluginId: 'stat', |
||||
fieldConfig: { |
||||
defaults: { |
||||
displayName: 'Last', |
||||
links: [ |
||||
{ |
||||
title: 'Go to handler drilldown view', |
||||
url: ``, |
||||
onBuildUrl: () => { |
||||
const params = locationService.getSearchObject(); |
||||
return getLinkUrlWithAppUrlState( |
||||
'/scenes/grafana-monitoring/handlers/${__field.labels.handler:percentencode}', |
||||
params |
||||
); |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
overrides: [], |
||||
}, |
||||
options: { |
||||
graphMode: 'none', |
||||
textMode: 'value', |
||||
}, |
||||
}), |
||||
], |
||||
}); |
||||
}, |
||||
}); |
||||
|
||||
const layout = new SceneFlexLayout({ |
||||
children: [httpHandlersTable], |
||||
}); |
||||
|
||||
const sceneToggle = new SceneRadioToggle({ |
||||
options: [ |
||||
{ value: 'table', label: 'Table' }, |
||||
{ value: 'graphs', label: 'Graphs' }, |
||||
], |
||||
value: 'table', |
||||
onChange: (value) => { |
||||
if (value === 'table') { |
||||
layout.setState({ children: [httpHandlersTable] }); |
||||
} else { |
||||
layout.setState({ children: [graphsScene] }); |
||||
} |
||||
}, |
||||
}); |
||||
|
||||
const scene = new EmbeddedScene({ |
||||
$variables: getVariablesDefinitions(), |
||||
$data: httpHandlerQueries, |
||||
$timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }), |
||||
controls: [ |
||||
new VariableValueSelectors({}), |
||||
searchBox, |
||||
new SceneControlsSpacer(), |
||||
sceneToggle, |
||||
new SceneTimePicker({ isOnCanvas: true }), |
||||
new SceneRefreshPicker({ isOnCanvas: true }), |
||||
], |
||||
body: layout, |
||||
}); |
||||
|
||||
return scene; |
||||
} |
||||
|
||||
export function getHandlerDetailsScene(handler: string): EmbeddedScene { |
||||
const reqDurationTimeSeries = getTimeSeriesQuery({ |
||||
expr: `avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum{handler="${handler}"}[$__rate_interval])) * 1e3`, |
||||
legendFormat: '{{method}} {{handler}} (status = {{status_code}})', |
||||
}); |
||||
|
||||
const reqCountTimeSeries = getTimeSeriesQuery({ |
||||
expr: `sum without(job, instance) (rate(grafana_http_request_duration_seconds_count{handler="${handler}"}[$__rate_interval])) `, |
||||
legendFormat: '{{method}} {{handler}} (status = {{status_code}})', |
||||
}); |
||||
|
||||
const scene = new EmbeddedScene({ |
||||
$variables: getVariablesDefinitions(), |
||||
$timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }), |
||||
controls: [ |
||||
new VariableValueSelectors({}), |
||||
new SceneControlsSpacer(), |
||||
new SceneTimePicker({ isOnCanvas: true }), |
||||
new SceneRefreshPicker({ isOnCanvas: true }), |
||||
], |
||||
body: new SceneFlexLayout({ |
||||
direction: 'column', |
||||
children: [ |
||||
new VizPanel({ |
||||
$data: reqDurationTimeSeries, |
||||
pluginId: 'timeseries', |
||||
title: 'Request duration avg (ms)', |
||||
placement: {}, |
||||
//displayMode: 'transparent',
|
||||
options: {}, |
||||
}), |
||||
new VizPanel({ |
||||
$data: reqCountTimeSeries, |
||||
pluginId: 'timeseries', |
||||
title: 'Request count/s', |
||||
//displayMode: 'transparent',
|
||||
options: {}, |
||||
}), |
||||
], |
||||
}), |
||||
}); |
||||
|
||||
return scene; |
||||
} |
||||
|
||||
function getInstantQuery(query: Partial<PromQuery>): SceneQueryRunner { |
||||
return new SceneQueryRunner({ |
||||
datasource: { uid: 'gdev-prometheus' }, |
||||
queries: [ |
||||
{ |
||||
refId: 'A', |
||||
instant: true, |
||||
format: 'table', |
||||
maxDataPoints: 500, |
||||
...query, |
||||
}, |
||||
], |
||||
}); |
||||
} |
||||
|
||||
function getTimeSeriesQuery(query: Partial<PromQuery>): SceneQueryRunner { |
||||
return new SceneQueryRunner({ |
||||
datasource: { uid: 'gdev-prometheus' }, |
||||
queries: [ |
||||
{ |
||||
refId: 'A', |
||||
range: true, |
||||
format: 'time_series', |
||||
maxDataPoints: 500, |
||||
...query, |
||||
}, |
||||
], |
||||
}); |
||||
} |
||||
|
||||
export function getOverviewScene(): EmbeddedScene { |
||||
const scene = new EmbeddedScene({ |
||||
$variables: getVariablesDefinitions(), |
||||
$timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }), |
||||
controls: [ |
||||
new VariableValueSelectors({}), |
||||
new SceneControlsSpacer(), |
||||
new SceneTimePicker({ isOnCanvas: true }), |
||||
new SceneRefreshPicker({ isOnCanvas: true }), |
||||
], |
||||
body: new SceneFlexLayout({ |
||||
direction: 'column', |
||||
children: [ |
||||
new SceneFlexLayout({ |
||||
placement: { height: 150 }, |
||||
children: [ |
||||
getInstantStatPanel('grafana_stat_totals_dashboard', 'Dashboards'), |
||||
getInstantStatPanel('grafana_stat_total_users', 'Users'), |
||||
getInstantStatPanel('sum(grafana_stat_totals_datasource)', 'Data sources'), |
||||
getInstantStatPanel('grafana_stat_total_service_account_tokens', 'Service account tokens'), |
||||
], |
||||
}), |
||||
new VizPanel({ |
||||
$data: new SceneQueryRunner({ |
||||
datasource: { uid: 'gdev-prometheus' }, |
||||
queries: [ |
||||
{ |
||||
refId: 'A', |
||||
expr: `sum(process_resident_memory_bytes{job="grafana", instance=~"$instance"})`, |
||||
range: true, |
||||
format: 'time_series', |
||||
maxDataPoints: 500, |
||||
}, |
||||
], |
||||
}), |
||||
pluginId: 'timeseries', |
||||
title: 'Memory usage', |
||||
options: { |
||||
legend: { |
||||
showLegend: false, |
||||
}, |
||||
}, |
||||
fieldConfig: { |
||||
defaults: { |
||||
unit: 'bytes', |
||||
min: 0, |
||||
custom: { |
||||
lineWidth: 2, |
||||
fillOpacity: 6, |
||||
//gradientMode: 'opacity',
|
||||
}, |
||||
}, |
||||
overrides: [], |
||||
}, |
||||
}), |
||||
new VizPanel({ |
||||
$data: new SceneQueryRunner({ |
||||
datasource: { uid: 'gdev-prometheus' }, |
||||
queries: [ |
||||
{ |
||||
refId: 'A', |
||||
expr: `sum(go_goroutines{job="grafana", instance=~"$instance"})`, |
||||
range: true, |
||||
format: 'time_series', |
||||
maxDataPoints: 500, |
||||
}, |
||||
], |
||||
}), |
||||
pluginId: 'timeseries', |
||||
title: 'Go routines', |
||||
options: { |
||||
legend: { |
||||
showLegend: false, |
||||
}, |
||||
}, |
||||
fieldConfig: { |
||||
defaults: { |
||||
min: 0, |
||||
custom: { |
||||
lineWidth: 2, |
||||
fillOpacity: 6, |
||||
//gradientMode: 'opacity',
|
||||
}, |
||||
}, |
||||
overrides: [], |
||||
}, |
||||
}), |
||||
], |
||||
}), |
||||
}); |
||||
|
||||
return scene; |
||||
} |
||||
|
||||
function getVariablesDefinitions() { |
||||
return new SceneVariableSet({ |
||||
variables: [ |
||||
new QueryVariable({ |
||||
name: 'instance', |
||||
datasource: { uid: 'gdev-prometheus' }, |
||||
query: { query: 'label_values(grafana_http_request_duration_seconds_sum, instance)' }, |
||||
}), |
||||
], |
||||
}); |
||||
} |
||||
|
||||
function getInstantStatPanel(query: string, title: string) { |
||||
return new VizPanel({ |
||||
$data: getInstantQuery({ expr: query }), |
||||
pluginId: 'stat', |
||||
title, |
||||
options: {}, |
||||
fieldConfig: { |
||||
defaults: { |
||||
color: { fixedColor: 'text', mode: FieldColorModeId.Fixed }, |
||||
}, |
||||
overrides: [], |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
export function getHandlerLogsScene(handler: string): EmbeddedScene { |
||||
const logsQuery = new SceneQueryRunner({ |
||||
datasource: { uid: 'gdev-loki' }, |
||||
queries: [ |
||||
{ |
||||
refId: 'A', |
||||
expr: `{job="grafana"} | logfmt | handler=\`${handler}\` | __error__=\`\``, |
||||
queryType: 'range', |
||||
maxDataPoints: 5000, |
||||
}, |
||||
], |
||||
}); |
||||
|
||||
const scene = new EmbeddedScene({ |
||||
$variables: getVariablesDefinitions(), |
||||
$timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }), |
||||
controls: [ |
||||
new VariableValueSelectors({}), |
||||
new SceneControlsSpacer(), |
||||
new SceneTimePicker({ isOnCanvas: true }), |
||||
new SceneRefreshPicker({ isOnCanvas: true }), |
||||
], |
||||
body: new SceneFlexLayout({ |
||||
direction: 'column', |
||||
children: [ |
||||
new VizPanel({ |
||||
pluginId: 'text', |
||||
title: '', |
||||
options: { |
||||
mode: 'markdown', |
||||
content: ` |
||||
[mupp](/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}/logs/mupp) |
||||
[mapp](/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}/logs/mapp) |
||||
`,
|
||||
}, |
||||
}), |
||||
new VizPanel({ |
||||
$data: logsQuery, |
||||
pluginId: 'logs', |
||||
title: '', |
||||
options: { |
||||
showTime: true, |
||||
showLabels: false, |
||||
showCommonLabels: false, |
||||
wrapLogMessage: true, |
||||
prettifyLogMessage: false, |
||||
enableLogDetails: true, |
||||
dedupStrategy: 'none', |
||||
sortOrder: 'Descending', |
||||
}, |
||||
}), |
||||
], |
||||
}), |
||||
}); |
||||
|
||||
return scene; |
||||
} |
||||
|
||||
export function getOverviewLogsScene(): EmbeddedScene { |
||||
const logsQuery = new SceneQueryRunner({ |
||||
datasource: { uid: 'gdev-loki' }, |
||||
queries: [ |
||||
{ |
||||
refId: 'A', |
||||
expr: `{job="grafana"} | logfmt | __error__=\`\``, |
||||
queryType: 'range', |
||||
maxDataPoints: 5000, |
||||
}, |
||||
], |
||||
}); |
||||
|
||||
const scene = new EmbeddedScene({ |
||||
$variables: getVariablesDefinitions(), |
||||
$timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }), |
||||
controls: [ |
||||
new VariableValueSelectors({}), |
||||
new SceneControlsSpacer(), |
||||
new SceneTimePicker({ isOnCanvas: true }), |
||||
new SceneRefreshPicker({ isOnCanvas: true }), |
||||
], |
||||
body: new SceneFlexLayout({ |
||||
direction: 'column', |
||||
children: [ |
||||
new VizPanel({ |
||||
$data: logsQuery, |
||||
pluginId: 'logs', |
||||
title: '', |
||||
options: { |
||||
showTime: true, |
||||
showLabels: false, |
||||
showCommonLabels: false, |
||||
wrapLogMessage: true, |
||||
prettifyLogMessage: false, |
||||
enableLogDetails: true, |
||||
dedupStrategy: 'none', |
||||
sortOrder: 'Descending', |
||||
}, |
||||
}), |
||||
], |
||||
}), |
||||
}); |
||||
|
||||
return scene; |
||||
} |
@ -0,0 +1,42 @@ |
||||
import { map } from 'rxjs'; |
||||
|
||||
import { |
||||
BasicValueMatcherOptions, |
||||
CustomTransformOperator, |
||||
DataTransformerID, |
||||
getFrameDisplayName, |
||||
ValueMatcherID, |
||||
} from '@grafana/data'; |
||||
import { FilterByValueMatch, FilterByValueType } from '@grafana/data/src/transformations/transformers/filterByValue'; |
||||
import { DataTransformerConfig, MatcherConfig } from '@grafana/schema'; |
||||
|
||||
export function getTableFilterTransform(query: string): DataTransformerConfig { |
||||
const regex: MatcherConfig<BasicValueMatcherOptions<string>> = { |
||||
id: ValueMatcherID.regex, |
||||
options: { value: query }, |
||||
}; |
||||
|
||||
return { |
||||
id: DataTransformerID.filterByValue, |
||||
options: { |
||||
type: FilterByValueType.include, |
||||
match: FilterByValueMatch.all, |
||||
filters: [ |
||||
{ |
||||
fieldName: 'handler', |
||||
config: regex, |
||||
}, |
||||
], |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
export function getTimeSeriesFilterTransform(query: string): CustomTransformOperator { |
||||
return () => (source) => { |
||||
return source.pipe( |
||||
map((data) => { |
||||
return data.filter((frame) => getFrameDisplayName(frame).toLowerCase().includes(query.toLowerCase())); |
||||
}) |
||||
); |
||||
}; |
||||
} |
@ -0,0 +1,13 @@ |
||||
import { useLocation } from 'react-router-dom'; |
||||
|
||||
import { UrlQueryMap, urlUtil } from '@grafana/data'; |
||||
import { locationSearchToObject } from '@grafana/runtime'; |
||||
|
||||
export function useAppQueryParams() { |
||||
const location = useLocation(); |
||||
return locationSearchToObject(location.search || ''); |
||||
} |
||||
|
||||
export function getLinkUrlWithAppUrlState(path: string, params: UrlQueryMap): string { |
||||
return urlUtil.renderUrl(path, params); |
||||
} |
Loading…
Reference in new issue