ui-dashboard-composer
Build KPI dashboards with charts and real-time data from SAP systems to visualize business metrics and monitor performance.
Instructions
Creates comprehensive KPI dashboards with charts and real-time data
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dashboardTitle | Yes | Title for the dashboard | |
| layout | No | Dashboard layout style | |
| widgets | Yes | Dashboard widget configurations |
Implementation Reference
- src/tools/ui/ui-dashboard-composer-tool.ts:107-150 (registration)Registers the 'ui-dashboard-composer' tool with the MCP server, defining title, description, input schema, and handler function.public async register(): Promise<void> { this.mcpServer.registerTool( "ui-dashboard-composer", { title: "UI Dashboard Composer", description: `Create interactive KPI dashboards with widgets, charts, and real-time updates. Features: - Multiple widget types (KPI cards, charts, tables, gauges, timelines) - Flexible grid-based layout system - Real-time data refresh and updates - Interactive filters and drill-down capabilities - Chart.js integration for advanced visualizations - Export to PDF, Excel, PowerPoint, and images - Responsive design for mobile and desktop - SAP Fiori design language compliance - Custom aggregations and calculations - WebSocket support for live data Required scope: ui.dashboards Widget Types: - kpi-card: Key performance indicator cards - chart: Bar, line, pie, doughnut, and radar charts - table: Data tables with sorting and filtering - list: Simple list displays - gauge: Circular and linear progress gauges - timeline: Event timelines and process flows - map: Geographic data visualization - custom: Custom HTML/JavaScript widgets Examples: - Sales dashboard: {"title": "Sales Overview", "widgets": [...]} - Executive summary: {"title": "Executive KPIs", "layout": {"type": "grid", "columns": 3}} - Real-time monitoring: {"refreshInterval": 30, "widgets": [...]}`, inputSchema: UIDashboardComposerSchema }, async (args: Record<string, unknown>) => { return await this.handleDashboardComposition(args); } ); this.logger.info("β UI Dashboard Composer tool registered successfully"); }
- Zod-based input schema defining dashboard configuration: title, layout, widgets with positions/data sources, filters, theme, and export options.const UIDashboardComposerSchema = { title: z.string().describe("Dashboard title"), description: z.string().optional().describe("Dashboard description"), layout: z.object({ type: z.enum(['grid', 'flexbox', 'absolute']).describe("Layout type"), columns: z.number().min(1).max(12).optional().describe("Number of columns for grid layout"), gap: z.string().optional().describe("Gap between widgets (e.g., '1rem')"), responsive: z.boolean().optional().describe("Enable responsive design") }).describe("Dashboard layout configuration"), widgets: z.array(z.object({ id: z.string().describe("Unique widget identifier"), type: z.enum(['kpi-card', 'chart', 'table', 'list', 'gauge', 'timeline', 'map', 'custom']).describe("Widget type"), title: z.string().describe("Widget title"), position: z.object({ row: z.number().min(0).describe("Grid row position"), col: z.number().min(0).describe("Grid column position"), width: z.number().min(1).max(12).describe("Widget width (grid columns)"), height: z.number().min(1).describe("Widget height (grid rows)") }).describe("Widget position and size"), config: z.record(z.any()).optional().describe("Widget-specific configuration"), dataSource: z.object({ entitySet: z.string().describe("SAP entity set for data"), query: z.object({ filter: z.string().optional(), select: z.string().optional(), orderby: z.string().optional(), top: z.number().optional() }).optional().describe("OData query parameters"), aggregation: z.object({ groupBy: z.array(z.string()).optional().describe("Fields to group by"), measures: z.array(z.object({ field: z.string(), operation: z.enum(['sum', 'avg', 'count', 'min', 'max']) })).optional().describe("Aggregation measures") }).optional().describe("Data aggregation configuration"), refresh: z.number().optional().describe("Refresh interval in seconds") }).describe("Data source configuration") })).describe("Dashboard widgets"), datasources: z.array(z.object({ id: z.string().describe("Data source identifier"), entitySet: z.string().describe("SAP entity set"), query: z.string().optional().describe("Custom OData query"), cacheTtl: z.number().optional().describe("Cache TTL in seconds"), transform: z.string().optional().describe("Data transformation function name") })).optional().describe("Shared data sources"), refreshInterval: z.number().min(5).max(3600).optional().describe("Global refresh interval in seconds"), theme: z.enum(['sap_horizon', 'sap_fiori_3', 'dark', 'light']).optional().describe("Dashboard theme"), filters: z.array(z.object({ field: z.string().describe("Filter field name"), label: z.string().describe("Filter display label"), type: z.enum(['select', 'daterange', 'text', 'number']).describe("Filter type"), options: z.array(z.object({ value: z.string(), label: z.string() })).optional().describe("Options for select filters"), defaultValue: z.any().optional().describe("Default filter value") })).optional().describe("Global dashboard filters"), exportOptions: z.object({ pdf: z.boolean().optional(), excel: z.boolean().optional(), powerpoint: z.boolean().optional(), image: z.boolean().optional() }).optional().describe("Export format options") };
- Primary tool handler: validates args with schema, checks auth, prepares widgets/data/layout, generates UI via component library, enhances with SAP styles/JS, returns formatted text summary + JSON config.private async handleDashboardComposition(args: unknown): Promise<any> { try { // Validate input parameters const params = z.object(UIDashboardComposerSchema).parse(args); this.logger.info(`π Generating dashboard: ${params.title} with ${params.widgets.length} widgets`); // Check authentication and authorization const authCheck = await this.checkUIAccess('ui.dashboards'); if (!authCheck.hasAccess) { return { content: [{ type: "text", text: `β Authorization denied: ${authCheck.reason || 'Access denied for UI dashboard generation'}\n\nRequired scope: ui.dashboards` }] }; } // Step 1: Validate and prepare widget configurations const validatedWidgets = await this.validateAndPrepareWidgets(params.widgets); // Step 2: Prepare data sources const dataSources = await this.prepareDashboardDataSources(validatedWidgets, params.datasources); // Step 3: Create dashboard layout const layoutDefinition: LayoutDefinition = { type: params.layout.type, config: { columns: params.layout.columns || 4, gap: params.layout.gap || '1rem', responsive: params.layout.responsive !== false ? { breakpoints: { mobile: { columns: 1, gap: '0.5rem' }, tablet: { columns: 2, gap: '1rem' }, desktop: { columns: 4, gap: '1rem' } } } : undefined }, components: this.createWidgetComponents(validatedWidgets) }; // Step 4: Create dashboard configuration const dashboardConfig: DashboardConfig = { layout: layoutDefinition, widgets: validatedWidgets, datasources: dataSources, refreshInterval: params.refreshInterval || 60, theme: params.theme || 'sap_horizon' }; // Step 5: Generate dashboard UI const dashboardResult = await this.componentLibrary.generateDashboard(dashboardConfig); // Step 6: Add SAP-specific enhancements const enhancedResult = await this.enhanceDashboardResult(dashboardResult, params); // Step 7: Prepare response const response = this.createDashboardResponse(enhancedResult, params, dataSources); this.logger.info(`β Dashboard '${params.title}' generated successfully`); return { content: [ { type: "text", text: `# ${params.title}\n\n` + `${params.description || 'Interactive SAP dashboard with real-time KPIs'}\n\n` + `## Dashboard Overview:\n` + `- Widgets: ${params.widgets.length}\n` + `- Layout: ${params.layout.type} (${params.layout.columns || 4} columns)\n` + `- Theme: ${params.theme || 'sap_horizon'}\n` + `- Refresh: Every ${params.refreshInterval || 60} seconds\n` + `- Filters: ${params.filters?.length || 0}\n\n` + `## Widget Types:\n` + params.widgets.map(w => `- ${w.type}: ${w.title}`).join('\n') + '\n\n' + `## Data Sources:\n` + `- Entity Sets: ${Array.from(new Set(params.widgets.map(w => w.dataSource.entitySet))).join(', ')}\n` + `- Real-time Updates: ${params.refreshInterval ? 'β ' : 'β'}\n\n` + `## Features:\n` + `- Export Options: ${Object.entries(params.exportOptions || {}).filter(([k, v]) => v).map(([k]) => k.toUpperCase()).join(', ') || 'Basic'}\n` + `- Responsive Design: β \n` + `- Interactive Filters: ${params.filters?.length ? 'β ' : 'β'}\n\n` + `Embed this dashboard in your SAP application or use via MCP client.` }, { type: "resource", data: response, mimeType: "application/json" } ] }; } catch (error) { this.logger.error(`β Failed to generate dashboard`, error as Error); return { content: [{ type: "text", text: `β Failed to generate dashboard: ${(error as Error).message}` }] }; } }
- Validates widget configs, fetches/generates mock data based on type (KPI, chart, table, etc.), prepares for rendering.private async validateAndPrepareWidgets(widgets: any[]): Promise<WidgetConfig[]> { const validatedWidgets: WidgetConfig[] = []; for (const widget of widgets) { // Fetch mock data for the widget const widgetData = await this.fetchWidgetData(widget); const validatedWidget: WidgetConfig = { id: widget.id, type: widget.type, title: widget.title, position: widget.position, config: { ...widget.config, data: widgetData, theme: widget.config?.theme || 'sap_horizon' }, dataKey: widget.dataSource.entitySet }; validatedWidgets.push(validatedWidget); } return validatedWidgets; }
- Generates comprehensive SAP-themed CSS including Chart.js integration, responsive grid layout, widget styles (KPI cards, charts, tables, gauges, timelines), filters, and controls.private generateSAPDashboardCSS(theme: string): string { return ` /* Chart.js CDN - Include in head */ @import url('https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.min.css'); /* SAP ${theme} Dashboard Styles */ .sap-dashboard { background: #f5f6fa; min-height: 100vh; padding: 1.5rem; font-family: '72', -apple-system, BlinkMacSystemFont, sans-serif; } .sap-dashboard-header { background: white; border-radius: 8px; padding: 1.5rem; margin-bottom: 1.5rem; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); border: 1px solid #e4e7ea; } .sap-dashboard-title { font-size: 1.8rem; font-weight: 600; color: #0070f2; margin: 0 0 0.5rem 0; } .sap-dashboard-description { color: #6a6d70; margin: 0; font-size: 1rem; } .sap-dashboard-filters { display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap; } .sap-dashboard-filter { display: flex; flex-direction: column; gap: 0.25rem; } .sap-dashboard-filter label { font-size: 0.875rem; font-weight: 600; color: #32363a; } .sap-dashboard-filter select, .sap-dashboard-filter input { padding: 0.5rem; border: 1px solid #d5d9dc; border-radius: 4px; font-size: 0.875rem; min-width: 150px; } .sap-dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-top: 1.5rem; } .sap-dashboard-widget { background: white; border-radius: 8px; padding: 1.5rem; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); border: 1px solid #e4e7ea; transition: transform 0.2s ease, box-shadow 0.2s ease; position: relative; overflow: hidden; } .sap-dashboard-widget:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } .sap-widget-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; padding-bottom: 0.75rem; border-bottom: 1px solid #f0f2f5; } .sap-widget-title { font-size: 1.1rem; font-weight: 600; color: #32363a; margin: 0; } .sap-widget-actions { display: flex; gap: 0.25rem; } .sap-widget-action { background: none; border: none; color: #6a6d70; cursor: pointer; padding: 0.25rem; border-radius: 4px; transition: color 0.2s ease, background 0.2s ease; } .sap-widget-action:hover { color: #0070f2; background: #f0f8ff; } /* KPI Card Styles */ .sap-kpi-card { text-align: center; } .sap-kpi-value { font-size: 2.5rem; font-weight: 700; color: #0070f2; margin: 0.5rem 0; line-height: 1; } .sap-kpi-label { font-size: 0.875rem; color: #6a6d70; margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.5px; } .sap-kpi-trend { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.25rem 0.75rem; border-radius: 16px; font-size: 0.75rem; font-weight: 600; margin-top: 0.5rem; } .sap-kpi-trend.positive { background: #e8f5e8; color: #2e7d32; } .sap-kpi-trend.negative { background: #ffebee; color: #c62828; } .sap-kpi-trend.warning { background: #fff8e1; color: #f57c00; } /* Chart Container */ .sap-chart-container { position: relative; height: 300px; margin: 1rem 0; } .sap-chart-container canvas { max-height: 100%; } /* Table Styles */ .sap-widget-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; } .sap-widget-table th { background: #f7f8fa; border-bottom: 1px solid #e4e7ea; padding: 0.5rem; text-align: left; font-weight: 600; color: #32363a; font-size: 0.75rem; text-transform: uppercase; } .sap-widget-table td { padding: 0.5rem; border-bottom: 1px solid #f0f2f5; color: #32363a; } .sap-widget-table tr:last-child td { border-bottom: none; } /* Gauge Styles */ .sap-gauge-container { display: flex; flex-direction: column; align-items: center; text-align: center; } .sap-gauge-value { font-size: 2rem; font-weight: 700; color: #0070f2; margin: 1rem 0 0.5rem 0; } .sap-gauge-label { font-size: 0.875rem; color: #6a6d70; } /* Timeline Styles */ .sap-timeline { max-height: 400px; overflow-y: auto; } .sap-timeline-item { display: flex; align-items: flex-start; gap: 1rem; padding: 0.75rem 0; border-bottom: 1px solid #f0f2f5; } .sap-timeline-item:last-child { border-bottom: none; } .sap-timeline-icon { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #e3f2fd; color: #0070f2; font-size: 1rem; flex-shrink: 0; } .sap-timeline-content { flex: 1; } .sap-timeline-title { font-weight: 600; color: #32363a; margin: 0 0 0.25rem 0; font-size: 0.875rem; } .sap-timeline-description { color: #6a6d70; font-size: 0.75rem; margin: 0 0 0.25rem 0; } .sap-timeline-date { color: #6a6d70; font-size: 0.75rem; font-family: 'Courier New', monospace; } /* Dashboard Controls */ .sap-dashboard-controls { position: fixed; top: 1rem; right: 1rem; display: flex; gap: 0.5rem; z-index: 1000; } .sap-control-button { background: white; border: 1px solid #d5d9dc; border-radius: 4px; padding: 0.5rem; cursor: pointer; color: #32363a; transition: all 0.2s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .sap-control-button:hover { background: #f7f8fa; border-color: #0070f2; color: #0070f2; } /* Responsive Design */ @media (max-width: 768px) { .sap-dashboard { padding: 1rem; } .sap-dashboard-grid { grid-template-columns: 1fr; gap: 1rem; } .sap-dashboard-filters { flex-direction: column; } .sap-dashboard-filter select, .sap-dashboard-filter input { min-width: auto; width: 100%; } .sap-kpi-value { font-size: 2rem; } .sap-chart-container { height: 250px; } } `;