diff --git a/package.json b/package.json
index f6531baebc3..3f77100ddd2 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts
index 46e9c8b1689..090d37bfd01 100644
--- a/packages/grafana-data/src/types/fieldOverrides.ts
+++ b/packages/grafana-data/src/types/fieldOverrides.ts
@@ -132,4 +132,5 @@ export enum FieldConfigProperty {
Mappings = 'mappings',
Links = 'links',
Color = 'color',
+ Filterable = 'filterable',
}
diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx
index a24289f01db..19519613225 100644
--- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx
+++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx
@@ -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};
diff --git a/public/app/features/scenes/SceneEmbeddedPage.tsx b/public/app/features/scenes/SceneEmbeddedPage.tsx
deleted file mode 100644
index 7b7b2c06500..00000000000
--- a/public/app/features/scenes/SceneEmbeddedPage.tsx
+++ /dev/null
@@ -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
Scene not found
;
- }
-
- const pageNav: NavModelItem = {
- text: 'Embedded Scene',
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default SceneEmbeddedPage;
diff --git a/public/app/features/scenes/SceneListPage.tsx b/public/app/features/scenes/SceneListPage.tsx
index 33078f91313..9310fd2802b 100644
--- a/public/app/features/scenes/SceneListPage.tsx
+++ b/public/app/features/scenes/SceneListPage.tsx
@@ -23,6 +23,12 @@ export const SceneListPage = ({}: Props) => {
+ Apps
+
+
+ Grafana monitoring
+
+
Test scenes
{scenes.map((scene) => (
diff --git a/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx b/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx
new file mode 100644
index 00000000000..e42b2cd068c
--- /dev/null
+++ b/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx
@@ -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({ sectionNav });
+
+ return (
+
+
+
+ );
+}
+
+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;
diff --git a/public/app/features/scenes/apps/SceneRadioToggle.tsx b/public/app/features/scenes/apps/SceneRadioToggle.tsx
new file mode 100644
index 00000000000..060b9327de2
--- /dev/null
+++ b/public/app/features/scenes/apps/SceneRadioToggle.tsx
@@ -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>;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export class SceneRadioToggle extends SceneObjectBase {
+ public onChange = (value: string) => {
+ this.setState({ value });
+ this.state.onChange(value);
+ };
+
+ public static Component = ({ model }: SceneComponentProps) => {
+ const { options, value } = model.useState();
+
+ return ;
+ };
+}
diff --git a/public/app/features/scenes/apps/SceneSearchBox.tsx b/public/app/features/scenes/apps/SceneSearchBox.tsx
new file mode 100644
index 00000000000..0a4a5dfb57c
--- /dev/null
+++ b/public/app/features/scenes/apps/SceneSearchBox.tsx
@@ -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 {
+ public onChange = (evt: React.FormEvent) => {
+ this.setState({ value: evt.currentTarget.value });
+ };
+
+ public static Component = ({ model }: SceneComponentProps) => {
+ const { value } = model.useState();
+
+ return ;
+ };
+}
diff --git a/public/app/features/scenes/apps/scenes.ts b/public/app/features/scenes/apps/scenes.ts
new file mode 100644
index 00000000000..40aafdc3280
--- /dev/null
+++ b/public/app/features/scenes/apps/scenes.ts
@@ -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): SceneQueryRunner {
+ return new SceneQueryRunner({
+ datasource: { uid: 'gdev-prometheus' },
+ queries: [
+ {
+ refId: 'A',
+ instant: true,
+ format: 'table',
+ maxDataPoints: 500,
+ ...query,
+ },
+ ],
+ });
+}
+
+function getTimeSeriesQuery(query: Partial): 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;
+}
diff --git a/public/app/features/scenes/apps/transforms.ts b/public/app/features/scenes/apps/transforms.ts
new file mode 100644
index 00000000000..3bbdca4a394
--- /dev/null
+++ b/public/app/features/scenes/apps/transforms.ts
@@ -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> = {
+ 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()));
+ })
+ );
+ };
+}
diff --git a/public/app/features/scenes/apps/utils.ts b/public/app/features/scenes/apps/utils.ts
new file mode 100644
index 00000000000..3a3a1e35f25
--- /dev/null
+++ b/public/app/features/scenes/apps/utils.ts
@@ -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);
+}
diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx
index ea620348f38..c3bb8ac060d 100644
--- a/public/app/routes/routes.tsx
+++ b/public/app/routes/routes.tsx
@@ -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')
),
},
{
diff --git a/yarn.lock b/yarn.lock
index 2e901b98303..786797f0bc6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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