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
Torkel Ödegaard 2 years ago committed by GitHub
parent 769b4598d7
commit 5c9898a860
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      package.json
  2. 1
      packages/grafana-data/src/types/fieldOverrides.ts
  3. 4
      packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx
  4. 32
      public/app/features/scenes/SceneEmbeddedPage.tsx
  5. 6
      public/app/features/scenes/SceneListPage.tsx
  6. 144
      public/app/features/scenes/apps/GrafanaMonitoringApp.tsx
  7. 24
      public/app/features/scenes/apps/SceneRadioToggle.tsx
  8. 20
      public/app/features/scenes/apps/SceneSearchBox.tsx
  9. 536
      public/app/features/scenes/apps/scenes.ts
  10. 42
      public/app/features/scenes/apps/transforms.ts
  11. 13
      public/app/features/scenes/apps/utils.ts
  12. 5
      public/app/routes/routes.tsx
  13. 10
      yarn.lock

@ -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

Loading…
Cancel
Save