From 796c52da4dd83536281e37285ae4fe9ccb7d3b2d Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Mon, 30 Mar 2026 14:54:54 +0100 Subject: [PATCH] feat: Add dashboard template selection and creation functionality - Introduced DashboardTemplateCard component for displaying dashboard templates. - Added DashboardTemplates enum and DashboardTemplate interface to define available templates. - Implemented template selection in the Dashboards page to allow users to create dashboards from predefined templates. - Enhanced MetricQueryConfig to manage metric attributes and display settings more effectively. - Updated MetricView to improve loading states and error handling for metric results. - Refactored DashboardChartComponent to streamline metric alias data handling and improve UI presentation. --- .../Components/DashboardChartComponent.tsx | 29 +- .../Dashboard/DashboardTemplateCard.tsx | 40 ++ .../src/Components/Metrics/MetricAlias.tsx | 40 +- .../Components/Metrics/MetricQueryConfig.tsx | 482 +++++++++++++----- .../src/Components/Metrics/MetricView.tsx | 218 ++++---- .../src/Pages/Dashboards/Dashboards.tsx | 63 ++- Common/Types/Dashboard/DashboardTemplates.ts | 317 ++++++++++++ 7 files changed, 917 insertions(+), 272 deletions(-) create mode 100644 App/FeatureSet/Dashboard/src/Components/Dashboard/DashboardTemplateCard.tsx create mode 100644 Common/Types/Dashboard/DashboardTemplates.ts diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent.tsx index 6b5bcb128c..6ea5411326 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent.tsx @@ -209,22 +209,14 @@ const DashboardChartComponentElement: FunctionComponent = ( const chartMetricViewData: MetricViewData = { queryConfigs: queryConfigs.map( - (config: MetricQueryConfigData, index: number) => { + (config: MetricQueryConfigData) => { return { ...config, metricAliasData: { metricVariable: config.metricAliasData?.metricVariable || undefined, - title: - (index === 0 - ? config.metricAliasData?.title || - props.component.arguments.chartTitle - : config.metricAliasData?.title) || undefined, - description: - (index === 0 - ? config.metricAliasData?.description || - props.component.arguments.chartDescription - : config.metricAliasData?.description) || undefined, + title: config.metricAliasData?.title || undefined, + description: config.metricAliasData?.description || undefined, legend: config.metricAliasData?.legend || undefined, legendUnit: config.metricAliasData?.legendUnit || undefined, }, @@ -246,6 +238,21 @@ const DashboardChartComponentElement: FunctionComponent = ( transition: "opacity 0.2s ease-in-out", }} > + {(props.component.arguments.chartTitle || + props.component.arguments.chartDescription) && ( +
+ {props.component.arguments.chartTitle && ( +

+ {props.component.arguments.chartTitle} +

+ )} + {props.component.arguments.chartDescription && ( +

+ {props.component.arguments.chartDescription} +

+ )} +
+ )} void; +} + +const DashboardTemplateCard: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + return ( +
{ + if (e.key === "Enter" || e.key === " ") { + props.onClick(); + } + }} + > +
+
+ +
+

{props.title}

+
+

+ {props.description} +

+
+ ); +}; + +export default DashboardTemplateCard; diff --git a/App/FeatureSet/Dashboard/src/Components/Metrics/MetricAlias.tsx b/App/FeatureSet/Dashboard/src/Components/Metrics/MetricAlias.tsx index 325b7c6a0b..1bb2cf3a4b 100644 --- a/App/FeatureSet/Dashboard/src/Components/Metrics/MetricAlias.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Metrics/MetricAlias.tsx @@ -8,6 +8,7 @@ export interface ComponentProps { data: MetricAliasData; isFormula: boolean; onDataChanged: (data: MetricAliasData) => void; + hideVariableBadge?: boolean | undefined; } const MetricAlias: FunctionComponent = ( @@ -16,25 +17,26 @@ const MetricAlias: FunctionComponent = ( return (
- {/* Variable badge row */} - {((!props.isFormula && props.data.metricVariable) || - props.isFormula) && ( -
- {!props.isFormula && props.data.metricVariable && ( -
- {props.data.metricVariable} -
- )} - {props.isFormula && ( -
- -
- )} - - Display Settings - -
- )} + {/* Variable badge row — hidden when parent already shows it */} + {!props.hideVariableBadge && + ((!props.isFormula && props.data.metricVariable) || + props.isFormula) && ( +
+ {!props.isFormula && props.data.metricVariable && ( +
+ {props.data.metricVariable} +
+ )} + {props.isFormula && ( +
+ +
+ )} + + Display Settings + +
+ )} {/* Title and Description */}
diff --git a/App/FeatureSet/Dashboard/src/Components/Metrics/MetricQueryConfig.tsx b/App/FeatureSet/Dashboard/src/Components/Metrics/MetricQueryConfig.tsx index db924296de..aecd24e14d 100644 --- a/App/FeatureSet/Dashboard/src/Components/Metrics/MetricQueryConfig.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Metrics/MetricQueryConfig.tsx @@ -1,17 +1,15 @@ -import React, { FunctionComponent, ReactElement } from "react"; +import React, { FunctionComponent, ReactElement, useState } from "react"; import MetricAlias from "./MetricAlias"; import MetricQuery from "./MetricQuery"; import Card from "Common/UI/Components/Card/Card"; -import Button, { - ButtonSize, - ButtonStyleType, -} from "Common/UI/Components/Button/Button"; import MetricQueryConfigData from "Common/Types/Metrics/MetricQueryConfigData"; import MetricAliasData from "Common/Types/Metrics/MetricAliasData"; import MetricQueryData from "Common/Types/Metrics/MetricQueryData"; -import { GetReactElementFunction } from "Common/UI/Types/FunctionTypes"; import MetricType from "Common/Models/DatabaseModels/MetricType"; import Input, { InputType } from "Common/UI/Components/Input/Input"; +import Icon from "Common/UI/Components/Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import Dictionary from "Common/Types/Dictionary"; export interface ComponentProps { data: MetricQueryConfigData; @@ -35,6 +33,10 @@ export interface ComponentProps { const MetricGraphConfig: FunctionComponent = ( props: ComponentProps, ): ReactElement => { + const [isExpanded, setIsExpanded] = useState(true); + const [showDisplaySettings, setShowDisplaySettings] = + useState(false); + const defaultAliasData: MetricAliasData = { metricVariable: undefined, title: undefined, @@ -43,139 +45,365 @@ const MetricGraphConfig: FunctionComponent = ( legendUnit: undefined, }; - const getContent: GetReactElementFunction = (): ReactElement => { + // Compute active attribute count for the header summary + const attributes: Dictionary | undefined = ( + props.data?.metricQueryData?.filterData as Record + )?.["attributes"] as Dictionary | undefined; + + const activeAttributeCount: number = attributes + ? Object.keys(attributes).length + : 0; + + const metricName: string = + props.data?.metricQueryData?.filterData?.metricName?.toString() || + "No metric selected"; + + const aggregationType: string = + props.data?.metricQueryData?.filterData?.aggegationType?.toString() || + "Avg"; + + // Remove a single attribute filter + const handleRemoveAttribute: (key: string) => void = ( + key: string, + ): void => { + if (!attributes) { + return; + } + + const newAttributes: Dictionary = { + ...attributes, + }; + delete newAttributes[key]; + + const newFilterData: Record = { + ...(props.data.metricQueryData.filterData as Record), + }; + + if (Object.keys(newAttributes).length > 0) { + newFilterData["attributes"] = newAttributes; + } else { + delete newFilterData["attributes"]; + } + + if (props.onChange) { + props.onChange({ + ...props.data, + metricQueryData: { + ...props.data.metricQueryData, + filterData: newFilterData as MetricQueryData["filterData"], + }, + }); + } + }; + + // Clear all attribute filters + const handleClearAllAttributes: () => void = (): void => { + const newFilterData: Record = { + ...(props.data.metricQueryData.filterData as Record), + }; + delete newFilterData["attributes"]; + + if (props.onChange) { + props.onChange({ + ...props.data, + metricQueryData: { + ...props.data.metricQueryData, + filterData: newFilterData as MetricQueryData["filterData"], + }, + }); + } + }; + + const getHeader: () => ReactElement = (): ReactElement => { return ( -
- {/* Metric query selection — always on top */} - {props.data?.metricQueryData && ( - { - props.onBlur?.(); - props.onFocus?.(); - if (props.onChange) { - const selectedMetricName: string | undefined = - data.filterData?.metricName?.toString(); - const previousMetricName: string | undefined = - props.data?.metricQueryData?.filterData?.metricName?.toString(); - - // If metric changed, prefill all alias fields from MetricType - if ( - selectedMetricName && - selectedMetricName !== previousMetricName - ) { - const metricType: MetricType | undefined = - props.metricTypes.find((m: MetricType) => { - return m.name === selectedMetricName; - }); - - if (metricType) { - const currentAlias: MetricAliasData = - props.data.metricAliasData || defaultAliasData; - - props.onChange({ - ...props.data, - metricQueryData: data, - metricAliasData: { - ...currentAlias, - title: metricType.name || "", - description: metricType.description || "", - legend: metricType.name || "", - legendUnit: metricType.unit || "", - }, - }); - return; - } - } - - props.onChange({ ...props.data, metricQueryData: data }); - } - }} - metricTypes={props.metricTypes} - telemetryAttributes={props.telemetryAttributes} - onAdvancedFiltersToggle={props.onAdvancedFiltersToggle} - isAttributesLoading={props.attributesLoading} - attributesError={props.attributesError} - onAttributesRetry={props.onAttributesRetry} - /> - )} - - {/* Display settings — title, description, legend, unit */} -
- { - props.onBlur?.(); - props.onFocus?.(); - if (props.onChange) { - props.onChange({ ...props.data, metricAliasData: data }); - } - }} - isFormula={false} - /> -
- - {/* Thresholds */} -
-
- - { - props.onBlur?.(); - props.onFocus?.(); - if (props.onChange) { - props.onChange({ - ...props.data, - warningThreshold: value ? Number(value) : undefined, - }); - } - }} - placeholder="e.g. 80" - /> -
-
- - { - props.onBlur?.(); - props.onFocus?.(); - if (props.onChange) { - props.onChange({ - ...props.data, - criticalThreshold: value ? Number(value) : undefined, - }); - } - }} - placeholder="e.g. 95" - /> +
+
+ {/* Variable badge */} + {props.data?.metricAliasData?.metricVariable && ( +
+ {props.data.metricAliasData.metricVariable} +
+ )} + {/* Summary info */} +
+
+ + {metricName} + + + {aggregationType} + + {activeAttributeCount > 0 && ( + + + {activeAttributeCount}{" "} + {activeAttributeCount === 1 ? "filter" : "filters"} + + )} +
+ {props.data?.metricAliasData?.title && + props.data.metricAliasData.title !== metricName && ( +

+ {props.data.metricAliasData.title} +

+ )}
- {/* Remove button */} - {props.onRemove && ( -
- + {props.onRemove && ( + + )} +
+
+ ); + }; + + const getAttributeChips: () => ReactElement | null = (): ReactElement | null => { + if (!attributes || activeAttributeCount === 0) { + return null; + } + + return ( +
+ + Filtered by: + + {Object.entries(attributes).map( + ([key, value]: [string, string | number | boolean]) => { + return ( + + {key}: + {String(value)} + + + ); + }, + )} + {activeAttributeCount > 1 && ( + + )} +
+ ); + }; + + const getContent: () => ReactElement = (): ReactElement => { + return ( +
+ {/* Header with summary */} + {getHeader()} + + {/* Attribute filter chips - always visible */} + {!isExpanded && getAttributeChips()} + + {/* Expandable content */} + {isExpanded && ( +
+ {/* Metric query selection */} + {props.data?.metricQueryData && ( + { + props.onBlur?.(); + props.onFocus?.(); + if (props.onChange) { + const selectedMetricName: string | undefined = + data.filterData?.metricName?.toString(); + const previousMetricName: string | undefined = + props.data?.metricQueryData?.filterData?.metricName?.toString(); + + // If metric changed, prefill all alias fields from MetricType + if ( + selectedMetricName && + selectedMetricName !== previousMetricName + ) { + const metricType: MetricType | undefined = + props.metricTypes.find((m: MetricType) => { + return m.name === selectedMetricName; + }); + + if (metricType) { + const currentAlias: MetricAliasData = + props.data.metricAliasData || defaultAliasData; + + props.onChange({ + ...props.data, + metricQueryData: data, + metricAliasData: { + ...currentAlias, + title: metricType.name || "", + description: metricType.description || "", + legend: metricType.name || "", + legendUnit: metricType.unit || "", + }, + }); + return; + } + } + + props.onChange({ ...props.data, metricQueryData: data }); + } + }} + metricTypes={props.metricTypes} + telemetryAttributes={props.telemetryAttributes} + onAdvancedFiltersToggle={props.onAdvancedFiltersToggle} + isAttributesLoading={props.attributesLoading} + attributesError={props.attributesError} + onAttributesRetry={props.onAttributesRetry} + /> + )} + + {/* Attribute filter chips */} + {getAttributeChips()} + + {/* Display Settings - collapsible */} +
+ + + {showDisplaySettings && ( +
+ { + props.onBlur?.(); + props.onFocus?.(); + if (props.onChange) { + props.onChange({ + ...props.data, + metricAliasData: data, + }); + } + }} + isFormula={false} + hideVariableBadge={true} + /> + + {/* Thresholds */} +
+
+ + { + props.onBlur?.(); + props.onFocus?.(); + if (props.onChange) { + props.onChange({ + ...props.data, + warningThreshold: value + ? Number(value) + : undefined, + }); + } + }} + placeholder="e.g. 80" + /> +
+
+ + { + props.onBlur?.(); + props.onFocus?.(); + if (props.onChange) { + props.onChange({ + ...props.data, + criticalThreshold: value + ? Number(value) + : undefined, + }); + } + }} + placeholder="e.g. 95" + /> +
+
+
+ )} +
)} + {props.error && ( -

+

{props.error}

)} diff --git a/App/FeatureSet/Dashboard/src/Components/Metrics/MetricView.tsx b/App/FeatureSet/Dashboard/src/Components/Metrics/MetricView.tsx index 5e3e895ac9..c7c22bbf6b 100644 --- a/App/FeatureSet/Dashboard/src/Components/Metrics/MetricView.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Metrics/MetricView.tsx @@ -12,13 +12,11 @@ import Button, { ButtonStyleType, } from "Common/UI/Components/Button/Button"; import Text from "Common/Types/Text"; -import HorizontalRule from "Common/UI/Components/HorizontalRule/HorizontalRule"; import MetricsAggregationType from "Common/Types/Metrics/MetricsAggregationType"; import StartAndEndDate, { StartAndEndDateType, } from "Common/UI/Components/Date/StartAndEndDate"; import InBetween from "Common/Types/BaseDatabase/InBetween"; -import FieldLabelElement from "Common/UI/Components/Forms/Fields/FieldLabel"; import Card from "Common/UI/Components/Card/Card"; import AggregatedResult from "Common/Types/BaseDatabase/AggregatedResult"; import API from "Common/UI/Utils/API/API"; @@ -34,6 +32,7 @@ import MetricCharts from "./MetricCharts"; import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal"; import JSONFunctions from "Common/Types/JSONFunctions"; import MetricType from "Common/Models/DatabaseModels/MetricType"; +import IconProp from "Common/Types/Icon/IconProp"; const getFetchRelevantState: (data: MetricViewData) => unknown = ( data: MetricViewData, @@ -109,9 +108,9 @@ const MetricView: FunctionComponent = ( const [isPageLoading, setIsPageLoading] = useState(false); const [pageError, setPageError] = useState(""); - const [telemetryAttributes, setTelemetryAttributes] = useState>( - [], - ); + const [telemetryAttributes, setTelemetryAttributes] = useState< + Array + >([]); const [telemetryAttributesLoaded, setTelemetryAttributesLoaded] = useState(false); const [telemetryAttributesLoading, setTelemetryAttributesLoading] = @@ -305,29 +304,33 @@ const MetricView: FunctionComponent = ( return ( -
+
+ {/* Time range selector */} {!props.hideStartAndEndDate && ( -
- -
- - | null) => { - if (props.onChange) { - props.onChange({ - ...props.data, - startAndEndDate: startAndEndDate, - }); - } - }} - /> + +
+
+ + Time Range +
- -
+ | null) => { + if (props.onChange) { + props.onChange({ + ...props.data, + startAndEndDate: startAndEndDate, + }); + } + }} + /> +
+
)} + {/* Query configs */} {!props.hideQueryElements && (
{props.data.queryConfigs.map( @@ -382,104 +385,91 @@ const MetricView: FunctionComponent = ( )}
)} -
- {!props.hideQueryElements && ( -
+ {/* Formula configs and Add buttons */} + {!props.hideQueryElements && (
- {props.data.formulaConfigs.map( - (formulaConfig: MetricFormulaConfigData, index: number) => { - return ( - { - const newGraphConfigs: Array = [ - ...props.data.formulaConfigs, - ]; - newGraphConfigs[index] = data; - if (props.onChange) { - props.onChange({ - ...props.data, - formulaConfigs: newGraphConfigs, - }); - } - }} - data={formulaConfig} - onRemove={() => { - const newGraphConfigs: Array = [ - ...props.data.formulaConfigs, - ]; - newGraphConfigs.splice(index, 1); - if (props.onChange) { - props.onChange({ - ...props.data, - formulaConfigs: newGraphConfigs, - }); - } - }} - /> - ); - }, - )} -
-
-
-
-
- -
- )} + )} - {isMetricResultsLoading && } + {/* Chart results */} + {isMetricResultsLoading && } - {metricResultsError && } + {metricResultsError && } - {!isMetricResultsLoading && !metricResultsError && ( -
- {/** charts */} - -
- )} + {!isMetricResultsLoading && !metricResultsError && ( +
+ +
+ )} +
{showCannotRemoveOneRemainingQueryError ? ( = (): ReactElement => { + const [selectedTemplate, setSelectedTemplate] = + useState(null); + const [showCreateForm, setShowCreateForm] = useState(false); + + const handleTemplateClick: (type: DashboardTemplateType) => void = + useCallback((type: DashboardTemplateType): void => { + setSelectedTemplate(type); + setShowCreateForm(true); + }, []); + return ( = (): ReactElement => { }, ]} > + +
+ {DashboardTemplates.map( + (template: DashboardTemplate): ReactElement => { + return ( + { + handleTemplateClick(template.type); + }} + /> + ); + }, + )} +
+
+ modelType={Dashboard} id="dashboard-table" @@ -40,6 +88,7 @@ const Dashboards: FunctionComponent = (): ReactElement => { isCreateable={true} name="Dashboards" isViewable={true} + showCreateForm={showCreateForm} cardProps={{ title: "Dashboards", description: "Here is a list of dashboards for this project.", @@ -69,6 +118,18 @@ const Dashboards: FunctionComponent = (): ReactElement => { placeholder: "Description", }, ]} + onBeforeCreate={async (item: Dashboard, _miscDataProps: JSONObject): Promise => { + if (selectedTemplate && selectedTemplate !== DashboardTemplateType.Blank) { + const templateConfig: DashboardViewConfig | null = + getTemplateConfig(selectedTemplate); + if (templateConfig) { + item.dashboardViewConfig = templateConfig; + } + } + setSelectedTemplate(null); + setShowCreateForm(false); + return item; + }} saveFilterProps={{ tableId: "all-dashboards-table", }} diff --git a/Common/Types/Dashboard/DashboardTemplates.ts b/Common/Types/Dashboard/DashboardTemplates.ts new file mode 100644 index 0000000000..9149421b3a --- /dev/null +++ b/Common/Types/Dashboard/DashboardTemplates.ts @@ -0,0 +1,317 @@ +import DashboardViewConfig from "./DashboardViewConfig"; +import { ObjectType } from "../JSON"; +import DashboardSize from "./DashboardSize"; +import DashboardComponentType from "./DashboardComponentType"; +import DashboardChartType from "./Chart/ChartType"; +import ObjectID from "../ObjectID"; +import DashboardBaseComponent from "./DashboardComponents/DashboardBaseComponent"; +import IconProp from "../Icon/IconProp"; + +export enum DashboardTemplateType { + Blank = "Blank", + Monitor = "Monitor", + Incident = "Incident", + Kubernetes = "Kubernetes", +} + +export interface DashboardTemplate { + type: DashboardTemplateType; + name: string; + description: string; + icon: IconProp; +} + +export const DashboardTemplates: Array = [ + { + type: DashboardTemplateType.Blank, + name: "Blank Dashboard", + description: "Start from scratch with an empty dashboard.", + icon: IconProp.Add, + }, + { + type: DashboardTemplateType.Monitor, + name: "Monitor Dashboard", + description: + "Pre-configured with response time, uptime, and throughput widgets.", + icon: IconProp.Activity, + }, + { + type: DashboardTemplateType.Incident, + name: "Incident Dashboard", + description: + "Track active incidents, MTTR, MTTA, and view recent logs.", + icon: IconProp.Alert, + }, + { + type: DashboardTemplateType.Kubernetes, + name: "Kubernetes Dashboard", + description: + "Monitor CPU, memory, pod count, and resource usage over time.", + icon: IconProp.Kubernetes, + }, +]; + +function createTextComponent(data: { + text: string; + top: number; + left: number; + width: number; + height: number; + isBold?: boolean; +}): DashboardBaseComponent { + return { + _type: ObjectType.DashboardComponent, + componentType: DashboardComponentType.Text, + componentId: ObjectID.generate(), + topInDashboardUnits: data.top, + leftInDashboardUnits: data.left, + widthInDashboardUnits: data.width, + heightInDashboardUnits: data.height, + minHeightInDashboardUnits: 1, + minWidthInDashboardUnits: 3, + arguments: { + text: data.text, + isBold: data.isBold ?? false, + isItalic: false, + isUnderline: false, + isMarkdown: false, + }, + }; +} + +function createValueComponent(data: { + title: string; + top: number; + left: number; + width: number; +}): DashboardBaseComponent { + return { + _type: ObjectType.DashboardComponent, + componentType: DashboardComponentType.Value, + componentId: ObjectID.generate(), + topInDashboardUnits: data.top, + leftInDashboardUnits: data.left, + widthInDashboardUnits: data.width, + heightInDashboardUnits: 1, + minHeightInDashboardUnits: 1, + minWidthInDashboardUnits: 1, + arguments: { + title: data.title, + metricQueryConfig: { + metricQueryData: { + filterData: {}, + groupBy: undefined, + }, + }, + }, + }; +} + +function createChartComponent(data: { + title: string; + chartType: DashboardChartType; + top: number; + left: number; + width: number; + height: number; +}): DashboardBaseComponent { + return { + _type: ObjectType.DashboardComponent, + componentType: DashboardComponentType.Chart, + componentId: ObjectID.generate(), + topInDashboardUnits: data.top, + leftInDashboardUnits: data.left, + widthInDashboardUnits: data.width, + heightInDashboardUnits: data.height, + minHeightInDashboardUnits: 3, + minWidthInDashboardUnits: 6, + arguments: { + chartTitle: data.title, + chartType: data.chartType, + metricQueryConfig: { + metricAliasData: { + metricVariable: "a", + title: undefined, + description: undefined, + legend: undefined, + legendUnit: undefined, + }, + metricQueryData: { + filterData: {}, + groupBy: undefined, + }, + }, + }, + }; +} + +function createLogStreamComponent(data: { + title: string; + top: number; + left: number; + width: number; + height: number; +}): DashboardBaseComponent { + return { + _type: ObjectType.DashboardComponent, + componentType: DashboardComponentType.LogStream, + componentId: ObjectID.generate(), + topInDashboardUnits: data.top, + leftInDashboardUnits: data.left, + widthInDashboardUnits: data.width, + heightInDashboardUnits: data.height, + minHeightInDashboardUnits: 3, + minWidthInDashboardUnits: 6, + arguments: { + title: data.title, + maxRows: 50, + }, + }; +} + +function createMonitorDashboardConfig(): DashboardViewConfig { + const components: Array = [ + createTextComponent({ + text: "Monitor Dashboard", + top: 0, + left: 0, + width: 12, + height: 1, + isBold: true, + }), + createValueComponent({ title: "Response Time", top: 1, left: 0, width: 4 }), + createValueComponent({ title: "Uptime %", top: 1, left: 4, width: 4 }), + createValueComponent({ title: "Error Rate", top: 1, left: 8, width: 4 }), + createChartComponent({ + title: "Response Time Over Time", + chartType: DashboardChartType.Line, + top: 2, + left: 0, + width: 6, + height: 3, + }), + createChartComponent({ + title: "Request Throughput", + chartType: DashboardChartType.Area, + top: 2, + left: 6, + width: 6, + height: 3, + }), + ]; + + return { + _type: ObjectType.DashboardViewConfig, + components, + heightInDashboardUnits: Math.max( + DashboardSize.heightInDashboardUnits, + 5, + ), + }; +} + +function createIncidentDashboardConfig(): DashboardViewConfig { + const components: Array = [ + createTextComponent({ + text: "Incident Dashboard", + top: 0, + left: 0, + width: 12, + height: 1, + isBold: true, + }), + createValueComponent({ + title: "Active Incidents", + top: 1, + left: 0, + width: 4, + }), + createValueComponent({ title: "MTTR", top: 1, left: 4, width: 4 }), + createValueComponent({ title: "MTTA", top: 1, left: 8, width: 4 }), + createChartComponent({ + title: "Incidents Over Time", + chartType: DashboardChartType.Line, + top: 2, + left: 0, + width: 6, + height: 3, + }), + createLogStreamComponent({ + title: "Recent Logs", + top: 2, + left: 6, + width: 6, + height: 3, + }), + ]; + + return { + _type: ObjectType.DashboardViewConfig, + components, + heightInDashboardUnits: Math.max( + DashboardSize.heightInDashboardUnits, + 5, + ), + }; +} + +function createKubernetesDashboardConfig(): DashboardViewConfig { + const components: Array = [ + createTextComponent({ + text: "Kubernetes Dashboard", + top: 0, + left: 0, + width: 12, + height: 1, + isBold: true, + }), + createValueComponent({ title: "CPU Usage", top: 1, left: 0, width: 4 }), + createValueComponent({ + title: "Memory Usage", + top: 1, + left: 4, + width: 4, + }), + createValueComponent({ title: "Pod Count", top: 1, left: 8, width: 4 }), + createChartComponent({ + title: "CPU Usage Over Time", + chartType: DashboardChartType.Line, + top: 2, + left: 0, + width: 6, + height: 3, + }), + createChartComponent({ + title: "Memory Usage Over Time", + chartType: DashboardChartType.Area, + top: 2, + left: 6, + width: 6, + height: 3, + }), + ]; + + return { + _type: ObjectType.DashboardViewConfig, + components, + heightInDashboardUnits: Math.max( + DashboardSize.heightInDashboardUnits, + 5, + ), + }; +} + +export function getTemplateConfig( + type: DashboardTemplateType, +): DashboardViewConfig | null { + switch (type) { + case DashboardTemplateType.Monitor: + return createMonitorDashboardConfig(); + case DashboardTemplateType.Incident: + return createIncidentDashboardConfig(); + case DashboardTemplateType.Kubernetes: + return createKubernetesDashboardConfig(); + case DashboardTemplateType.Blank: + return null; + } +}