Skip to main content
The Wazuh Dashboard provides a flexible framework for creating custom dashboards that visualize security data according to your specific monitoring requirements. Custom dashboards enable you to aggregate metrics, charts, and visualizations to provide tailored views of your security posture.

Overview

Custom dashboards in Wazuh Dashboard are composed of panels that display visualizations, each querying security event data from the Wazuh indices. The dashboard framework supports:
  • Dynamic panel layouts with customizable grid positioning
  • Multiple visualization types including line charts, pie charts, histograms, and area charts
  • Real-time data queries using the OpenSearch query language
  • Interactive filtering and time range selection
  • Agent-specific views for pinned agent monitoring

Dashboard architecture

Custom dashboards are built using three core components:

Dashboard configuration

The DashboardConfig class defines the dashboard metadata and orchestrates panel generation:
export class CustomDashboardConfig extends DashboardConfig {
  constructor(indexPatternId: string) {
    super(
      indexPatternId,
      new CustomDashboardLayoutDefinition(indexPatternId)
    );
  }

  protected override getId(): string {
    return 'custom-security-dashboard-tab';
  }

  protected override getTitle(): string {
    return 'Custom Security Dashboard';
  }

  protected override getDescription(): string {
    return 'Custom security metrics and visualizations';
  }
}
Reference: plugins/main/common/dashboards/lib/dashboard-config-service.ts:77

Layout definition

The DashboardLayoutDefinition class specifies the grid layout and visualization placement:
export class CustomDashboardLayoutDefinition extends DashboardLayoutDefinition {
  constructor(indexPatternId: string) {
    super();
    this.setGridVisualizationPairs(
      {
        gridData: {
          w: 36,  // Width (48 units total)
          h: 12,  // Height
          x: 0,   // X position
          y: 0,   // Y position
        },
        savedVis: getVisStateAlertsTimeline(indexPatternId),
      },
      {
        gridData: {
          w: 12,
          h: 12,
          x: 36,
          y: 0,
        },
        savedVis: getVisStateTopAlertTypes(indexPatternId),
      }
    );
  }
}
Reference: plugins/main/common/dashboards/dashboard-definitions/overview/mitre/overview/dashboard.ts:13

Visualization definitions

Visualization definitions specify the chart type, data aggregations, and query filters:
const getVisStateAlertsTimeline = (indexPatternId: string) => {
  return {
    id: 'Custom-Alerts-Timeline',
    title: 'Security alerts over time',
    type: 'line',
    params: {
      type: 'line',
      addTooltip: true,
      addLegend: true,
      legendPosition: 'right',
      categoryAxes: [
        {
          id: 'CategoryAxis-1',
          type: 'category',
          position: 'bottom',
          show: true,
          labels: { show: true, filter: true },
        },
      ],
      valueAxes: [
        {
          id: 'ValueAxis-1',
          name: 'LeftAxis-1',
          type: 'value',
          position: 'left',
          title: { text: 'Count' },
        },
      ],
    },
    data: {
      searchSource: {
        query: {
          language: 'kuery',
          query: '',
        },
        filter: [],
        index: indexPatternId,
      },
      aggs: [
        {
          id: '1',
          enabled: true,
          type: 'count',
          schema: 'metric',
          params: {},
        },
        {
          id: '2',
          enabled: true,
          type: 'date_histogram',
          schema: 'segment',
          params: {
            field: 'timestamp',
            timeRange: { from: 'now-7d', to: 'now' },
            interval: 'auto',
          },
        },
      ],
    },
  };
};
Reference: plugins/main/public/components/overview/mitre/dashboard/dashboard-panels.ts:4

Creating a custom dashboard

Step 1: Define visualization states

Create visualization configuration functions for each panel:
// File: custom-dashboard-panels.ts

const getVisStateSecurityEvents = (indexPatternId: string) => {
  return {
    id: 'Custom-Security-Events',
    title: 'Security events by severity',
    type: 'pie',
    params: {
      type: 'pie',
      addTooltip: true,
      addLegend: true,
      legendPosition: 'right',
      isDonut: true,
    },
    data: {
      searchSource: {
        query: { language: 'kuery', query: '' },
        filter: [],
        index: indexPatternId,
      },
      aggs: [
        {
          id: '1',
          type: 'count',
          schema: 'metric',
        },
        {
          id: '2',
          type: 'terms',
          schema: 'segment',
          params: {
            field: 'rule.level',
            orderBy: '1',
            order: 'desc',
            size: 10,
          },
        },
      ],
    },
  };
};

Step 2: Create layout definition

Define the dashboard grid layout:
// File: custom-dashboard-layout.ts

import { DashboardLayoutDefinition } from '../lib/dashboard-config-service';
import { getVisStateSecurityEvents } from './custom-dashboard-panels';

export class CustomDashboardLayoutDefinition extends DashboardLayoutDefinition {
  constructor(indexPatternId: string) {
    super();
    this.setGridVisualizationPairs(
      {
        gridData: { w: 24, h: 14, x: 0, y: 0 },
        savedVis: getVisStateSecurityEvents(indexPatternId),
      }
      // Add additional panels here
    );
  }
}

Step 3: Create dashboard configuration

Implement the dashboard configuration class:
// File: custom-dashboard-config.ts

import { DashboardConfig } from '../lib/dashboard-config-service';
import { CustomDashboardLayoutDefinition } from './custom-dashboard-layout';

export class CustomDashboardConfig extends DashboardConfig {
  constructor(indexPatternId: string) {
    super(
      indexPatternId,
      new CustomDashboardLayoutDefinition(indexPatternId)
    );
  }

  protected override getId(): string {
    return 'custom-security-dashboard';
  }

  protected override getTitle(): string {
    return 'Custom Security Dashboard';
  }

  protected override getDescription(): string {
    return 'Customized security monitoring dashboard';
  }
}

Step 4: Register the dashboard component

Create the React component using the createDashboard helper:
// File: dashboard.tsx

import { createDashboard } from '../../../common/dashboards';
import { DashboardDataSource } from '../data-source';
import { DataSourceRepository } from '../repository';

export const CustomSecurityDashboard = createDashboard({
  DataSource: DashboardDataSource,
  DataSourceRepositoryCreator: DataSourceRepository,
  getDashboardPanels: [
    {
      dashboardId: 'custom-security-dashboard',
      className: 'custom-dashboard-container',
    },
  ],
});
Reference: plugins/main/public/components/common/dashboards/dashboard.tsx:134

Visualization types

Line chart

Temporal data visualization with trend analysis:
type: 'line'
params: {
  seriesParams: [{
    type: 'line',
    mode: 'normal',
    drawLinesBetweenPoints: true,
    showCircles: true,
    lineWidth: 2,
  }],
}

Pie chart

Proportional distribution of categorical data:
type: 'pie'
params: {
  isDonut: true,
  labels: { show: false, values: true },
}

Histogram

Bar chart for categorical comparisons:
type: 'histogram'
params: {
  seriesParams: [{
    type: 'histogram',
    mode: 'stacked',
  }],
}

Area chart

Stacked temporal visualizations:
type: 'area'
params: {
  seriesParams: [{
    type: 'histogram',
    mode: 'normal',
    interpolate: 'linear',
  }],
}
Reference: plugins/main/public/components/overview/mitre/dashboard/dashboard-panels.ts:163

Advanced customization

Adding custom filters

Implement managed filters for dynamic query modification:
export const CustomDashboard = createDashboard({
  DataSource: DashboardDataSource,
  DataSourceRepositoryCreator: DataSourceRepository,
  managedFilters: {
    severity: {
      field: 'rule.level',
      label: 'Severity level',
      options: [
        { value: '>=12', label: 'High' },
        { value: '>=7', label: 'Medium' },
        { value: '<7', label: 'Low' },
      ],
    },
  },
  getDashboardPanels: [{
    dashboardId: 'custom-dashboard-id',
  }],
});

Agent-specific dashboards

Create dashboards that display data for a specific agent:
getDashboardPanels: [
  {
    dashboardId: 'custom-overview-dashboard',
    agentDashboardId: 'custom-agent-dashboard',
  },
]
The framework automatically switches between overview and agent views based on context.

Custom aggregations

Implement complex data aggregations:
aggs: [
  {
    id: '1',
    type: 'count',
    schema: 'metric',
  },
  {
    id: '2',
    type: 'terms',
    schema: 'group',
    params: {
      field: 'rule.mitre.technique',
      orderBy: '1',
      order: 'desc',
      size: 5,
    },
  },
  {
    id: '3',
    type: 'terms',
    schema: 'segment',
    params: {
      field: 'rule.mitre.tactic',
      orderBy: '1',
      order: 'desc',
      size: 5,
    },
  },
]
Reference: plugins/main/public/components/overview/mitre/dashboard/dashboard-panels.ts:349

Time range configuration

Configure default time ranges for dashboard queries:
params: {
  field: 'timestamp',
  timeRange: { from: 'now-30d', to: 'now' },
  interval: 'auto',
}

Dashboard panel service

The DashboardPanelBuilderService automatically generates panel configurations from layout definitions:
class DashboardPanelBuilderService {
  getDashboardPanels(): DashboardByRendererPanels {
    // Iterates through grid visualization pairs
    // Assigns sequential panel IDs
    // Returns fully configured panel structure
  }
}
Reference: plugins/main/common/dashboards/lib/dashboard-config-service.ts:8

Query language

Dashboards support both Kuery and Lucene query languages:

Kuery syntax

searchSource: {
  query: {
    language: 'kuery',
    query: 'rule.level >= 12 and rule.groups: "malware"',
  },
}

Lucene syntax

searchSource: {
  query: {
    language: 'lucene',
    query: 'rule.level:[12 TO *] AND rule.groups:"authentication_failed"',
  },
}

Best practices

Performance optimization

  • Limit the number of panels per dashboard to maintain responsiveness
  • Use appropriate aggregation sizes to balance detail and performance
  • Configure reasonable default time ranges to avoid querying excessive data
  • Implement pagination for large result sets

Layout design

  • The grid system uses 48 horizontal units
  • Standard panel heights range from 12 to 16 units
  • Position related visualizations adjacent to each other
  • Place high-priority metrics in the top-left quadrant

Data accuracy

  • Specify appropriate time fields for temporal aggregations
  • Use correct field types in aggregation parameters
  • Test queries independently before integrating into dashboards
  • Validate index pattern availability

Maintainability

  • Organize visualization definitions in separate files
  • Use descriptive IDs and titles for panels
  • Document custom aggregation logic
  • Implement type safety with TypeScript interfaces

Troubleshooting

Dashboard not rendering

  • Verify the index pattern ID is correct and accessible
  • Check that all required fields exist in the index
  • Confirm the dashboard ID is unique and properly registered
  • Review browser console for configuration errors

Visualizations showing no data

  • Validate the time range includes relevant events
  • Verify aggregation field names match index schema
  • Check query syntax for errors
  • Confirm data exists for the specified filters

Layout issues

  • Ensure grid coordinates do not overlap
  • Verify total width does not exceed 48 units
  • Check that panel heights provide adequate space for content
  • Test layout on different screen resolutions
  • MITRE ATT&CK framework mapping for reference dashboard implementations
  • Dashboard renderer service for understanding the rendering pipeline
  • Data source configuration for custom data fetching logic

Build docs developers (and LLMs) love