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>
This commit is contained in:
co-authored by
Dominik Prokop
parent
769b4598d7
commit
5c9898a860
+1
-1
@@ -267,7 +267,7 @@
|
||||
"@grafana/lezer-logql": "0.1.2",
|
||||
"@grafana/monaco-logql": "^0.0.7",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "^0.0.19",
|
||||
"@grafana/scenes": "^0.0.21",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
"@kusto/monaco-kusto": "5.3.6",
|
||||
|
||||
@@ -132,4 +132,5 @@ export enum FieldConfigProperty {
|
||||
Mappings = 'mappings',
|
||||
Links = 'links',
|
||||
Color = 'color',
|
||||
Filterable = 'filterable',
|
||||
}
|
||||
|
||||
@@ -140,8 +140,8 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
||||
const defaultTopNav = css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
color: ${theme.colors.text.primary};
|
||||
|
||||
@@ -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;
|
||||
@@ -23,6 +23,12 @@ export const SceneListPage = ({}: Props) => {
|
||||
<Page navId="scenes" subTitle="Experimental new runtime and state model for dashboards">
|
||||
<Page.Contents>
|
||||
<Stack direction="column" gap={1}>
|
||||
<h5>Apps</h5>
|
||||
<Stack direction="column" gap={0}>
|
||||
<Card href={`/scenes/grafana-monitoring`}>
|
||||
<Card.Heading>Grafana monitoring</Card.Heading>
|
||||
</Card>
|
||||
</Stack>
|
||||
<h5>Test scenes</h5>
|
||||
<Stack direction="column" gap={0}>
|
||||
{scenes.map((scene) => (
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -580,9 +580,10 @@ export function getDynamicDashboardRoutes(cfg = config): RouteDescriptor[] {
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/scenes/embedded/:name',
|
||||
path: '/scenes/grafana-monitoring',
|
||||
exact: false,
|
||||
component: SafeDynamicImport(
|
||||
() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/SceneEmbeddedPage')
|
||||
() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/apps/GrafanaMonitoringApp')
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5209,9 +5209,9 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes@npm:^0.0.19":
|
||||
version: 0.0.19
|
||||
resolution: "@grafana/scenes@npm:0.0.19"
|
||||
"@grafana/scenes@npm:^0.0.21":
|
||||
version: 0.0.21
|
||||
resolution: "@grafana/scenes@npm:0.0.21"
|
||||
dependencies:
|
||||
"@grafana/e2e-selectors": ^9.4.3
|
||||
"@grafana/experimental": 1.0.1
|
||||
@@ -5219,7 +5219,7 @@ __metadata:
|
||||
react-use: 17.4.0
|
||||
react-virtualized-auto-sizer: 1.0.7
|
||||
uuid: ^9.0.0
|
||||
checksum: e71451751523c33a72d1fe777ac37795166da44c4316a8c1a00ffff936167d49eca91cbe1557a7ffbbfa220c58112dc5c84d45058725efd5c1bf3ba32c2cfb36
|
||||
checksum: a36d6b19884a240df1bc3048f72a2a5b7a505371ae30ae29f70f77d6c590d39282a027322b7d49233cb62fa9410eefedb6cb2cb53d938d9394d142ba5734a070
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -22120,7 +22120,7 @@ __metadata:
|
||||
"@grafana/lezer-logql": 0.1.2
|
||||
"@grafana/monaco-logql": ^0.0.7
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/scenes": ^0.0.19
|
||||
"@grafana/scenes": ^0.0.21
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/toolkit": "workspace:*"
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
|
||||
Reference in New Issue
Block a user