526 lines
11 KiB
Markdown
526 lines
11 KiB
Markdown
# Ejemplos de dashboards
|
|
|
|
## 1. Dashboard minimo — una BD, un KPI
|
|
|
|
El dashboard mas simple posible: una conexion SQLite y un numero.
|
|
|
|
```yaml
|
|
settings:
|
|
title: "Contador"
|
|
refresh: 5s
|
|
columns: 4
|
|
|
|
theme: "dark"
|
|
|
|
connections:
|
|
db:
|
|
driver: sqlite
|
|
path: ./data.db
|
|
|
|
queries:
|
|
total:
|
|
sql: "SELECT COUNT(*) as value FROM items"
|
|
connection: db
|
|
|
|
filters: {}
|
|
|
|
sections:
|
|
- id: main
|
|
title: "Total"
|
|
widgets:
|
|
- id: count
|
|
type: kpi
|
|
title: "Items"
|
|
query: total
|
|
mapping: { value: "value" }
|
|
span: 4
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Dashboard con filtros — Postgres + select + date range
|
|
|
|
Dashboard de ventas con filtros interactivos que re-ejecutan las queries.
|
|
|
|
```yaml
|
|
settings:
|
|
title: "Sales Dashboard"
|
|
refresh: 30s
|
|
columns: 12
|
|
|
|
theme: "dark"
|
|
|
|
connections:
|
|
sales:
|
|
driver: postgres
|
|
host: localhost
|
|
port: 5432
|
|
user: analytics
|
|
password: "${SALES_PW}"
|
|
database: sales
|
|
|
|
queries:
|
|
revenue_kpi:
|
|
connection: sales
|
|
sql: |
|
|
SELECT SUM(amount) as value, COUNT(*) as orders
|
|
FROM orders
|
|
WHERE created_at >= :from AND created_at <= :to
|
|
AND (:cat = 'all' OR category = :cat)
|
|
refresh: 15s
|
|
params:
|
|
from: "$filter.periodo.from"
|
|
to: "$filter.periodo.to"
|
|
cat: "$filter.categoria"
|
|
|
|
revenue_daily:
|
|
connection: sales
|
|
sql: |
|
|
SELECT date_trunc('day', created_at)::date as day,
|
|
SUM(amount) as revenue
|
|
FROM orders
|
|
WHERE created_at >= :from
|
|
AND (:cat = 'all' OR category = :cat)
|
|
GROUP BY day ORDER BY day
|
|
refresh: 30s
|
|
params:
|
|
from: "$filter.periodo.from"
|
|
cat: "$filter.categoria"
|
|
|
|
top_categories:
|
|
connection: sales
|
|
sql: |
|
|
SELECT category, SUM(amount) as revenue
|
|
FROM orders
|
|
WHERE created_at >= :from
|
|
GROUP BY category ORDER BY revenue DESC LIMIT 10
|
|
refresh: 30s
|
|
params:
|
|
from: "$filter.periodo.from"
|
|
|
|
recent_orders:
|
|
connection: sales
|
|
sql: |
|
|
SELECT id, customer, amount, category, status, created_at
|
|
FROM orders ORDER BY created_at DESC LIMIT 25
|
|
refresh: 10s
|
|
|
|
filters:
|
|
periodo:
|
|
type: date_range
|
|
label: "Periodo"
|
|
default: { from: "now-7d", to: "now" }
|
|
presets:
|
|
- { label: "Hoy", from: "now-0d", to: "now" }
|
|
- { label: "7d", from: "now-7d", to: "now" }
|
|
- { label: "30d", from: "now-30d", to: "now" }
|
|
- { label: "90d", from: "now-90d", to: "now" }
|
|
|
|
categoria:
|
|
type: select
|
|
label: "Categoria"
|
|
default: "all"
|
|
options:
|
|
- { label: "Todas", value: "all" }
|
|
- { label: "Electronics", value: "electronics" }
|
|
- { label: "Clothing", value: "clothing" }
|
|
- { label: "Food", value: "food" }
|
|
|
|
sections:
|
|
- id: kpis
|
|
title: "Resumen"
|
|
widgets:
|
|
- id: revenue
|
|
type: kpi
|
|
title: "Revenue"
|
|
query: revenue_kpi
|
|
mapping: { value: "value", format: "$,.2f" }
|
|
span: 4
|
|
|
|
- id: orders
|
|
type: kpi
|
|
title: "Ordenes"
|
|
query: revenue_kpi
|
|
mapping: { value: "orders", format: "," }
|
|
span: 4
|
|
|
|
- id: charts
|
|
title: "Tendencias"
|
|
columns: 2
|
|
widgets:
|
|
- id: revenue_line
|
|
type: line_chart
|
|
title: "Revenue diario"
|
|
query: revenue_daily
|
|
mapping:
|
|
x: "day"
|
|
series: [{ key: "revenue", name: "Revenue" }]
|
|
options: { curve: monotone, zoomable: true }
|
|
span: 1
|
|
|
|
- id: categories_bar
|
|
type: bar_chart
|
|
title: "Top categorias"
|
|
query: top_categories
|
|
mapping: { x: "category", y: "revenue" }
|
|
span: 1
|
|
|
|
- id: detail
|
|
title: "Ordenes recientes"
|
|
collapsible: true
|
|
widgets:
|
|
- id: orders_table
|
|
type: table
|
|
title: "Ultimas 25"
|
|
query: recent_orders
|
|
mapping:
|
|
columns:
|
|
- { key: "id", label: "ID" }
|
|
- { key: "customer", label: "Cliente" }
|
|
- { key: "amount", label: "Monto", format: "$,.2f" }
|
|
- { key: "category", label: "Categoria" }
|
|
- { key: "status", label: "Estado" }
|
|
- { key: "created_at", label: "Fecha", format: "datetime" }
|
|
span: 12
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Dashboard multi-BD — SQLite + DuckDB
|
|
|
|
Combina datos de distintas bases en un solo dashboard.
|
|
|
|
```yaml
|
|
settings:
|
|
title: "Multi-DB Analytics"
|
|
refresh: 10s
|
|
columns: 12
|
|
|
|
theme: "dark"
|
|
|
|
connections:
|
|
ops:
|
|
driver: sqlite
|
|
path: ./operations.db
|
|
warehouse:
|
|
driver: duckdb
|
|
path: ./analytics.duckdb
|
|
|
|
queries:
|
|
entities_count:
|
|
connection: ops
|
|
sql: "SELECT COUNT(*) as value FROM entities"
|
|
refresh: 5s
|
|
|
|
executions_count:
|
|
connection: ops
|
|
sql: "SELECT COUNT(*) as value FROM executions"
|
|
refresh: 5s
|
|
|
|
assertions_pass_rate:
|
|
connection: ops
|
|
sql: |
|
|
SELECT
|
|
ROUND(100.0 * SUM(CASE WHEN passed = 1 THEN 1 ELSE 0 END) / COUNT(*), 1) as value
|
|
FROM assertion_results
|
|
refresh: 10s
|
|
|
|
daily_events:
|
|
connection: warehouse
|
|
sql: |
|
|
SELECT date_trunc('day', ts) as day, COUNT(*) as events
|
|
FROM events GROUP BY day ORDER BY day DESC LIMIT 30
|
|
refresh: 30s
|
|
|
|
status_distribution:
|
|
connection: ops
|
|
sql: "SELECT status, COUNT(*) as count FROM entities GROUP BY status"
|
|
refresh: 10s
|
|
|
|
filters: {}
|
|
|
|
sections:
|
|
- id: kpis
|
|
title: "Operations"
|
|
widgets:
|
|
- id: entities
|
|
type: kpi
|
|
title: "Entities"
|
|
query: entities_count
|
|
mapping: { value: "value" }
|
|
span: 4
|
|
|
|
- id: executions
|
|
type: kpi
|
|
title: "Executions"
|
|
query: executions_count
|
|
mapping: { value: "value" }
|
|
span: 4
|
|
|
|
- id: pass_rate
|
|
type: kpi
|
|
title: "Assertion Pass %"
|
|
query: assertions_pass_rate
|
|
mapping: { value: "value" }
|
|
span: 4
|
|
|
|
- id: charts
|
|
title: "Visualizacion"
|
|
columns: 2
|
|
widgets:
|
|
- id: events_area
|
|
type: area_chart
|
|
title: "Eventos diarios (DuckDB)"
|
|
query: daily_events
|
|
mapping:
|
|
x: "day"
|
|
series: [{ key: "events", name: "Eventos" }]
|
|
options: { show_grid: true }
|
|
span: 1
|
|
|
|
- id: status_bar
|
|
type: bar_chart
|
|
title: "Distribucion por status (SQLite)"
|
|
query: status_distribution
|
|
mapping: { x: "status", y: "count" }
|
|
span: 1
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Dashboard tiempo real — refresh sub-segundo
|
|
|
|
Para metricas que cambian rapido (CPU, colas, precios).
|
|
|
|
```yaml
|
|
settings:
|
|
title: "Realtime Monitor"
|
|
refresh: 1s
|
|
columns: 12
|
|
|
|
theme: "dark"
|
|
|
|
connections:
|
|
metrics:
|
|
driver: sqlite
|
|
path: ./metrics.db
|
|
|
|
queries:
|
|
cpu_current:
|
|
connection: metrics
|
|
sql: "SELECT value FROM metrics WHERE key = 'cpu' ORDER BY ts DESC LIMIT 1"
|
|
refresh: 200ms
|
|
stale_time: 100ms
|
|
|
|
memory_current:
|
|
connection: metrics
|
|
sql: "SELECT value FROM metrics WHERE key = 'memory' ORDER BY ts DESC LIMIT 1"
|
|
refresh: 500ms
|
|
stale_time: 250ms
|
|
|
|
cpu_history:
|
|
connection: metrics
|
|
sql: |
|
|
SELECT ts, value FROM metrics
|
|
WHERE key = 'cpu' ORDER BY ts DESC LIMIT 60
|
|
refresh: 1s
|
|
|
|
queue_depth:
|
|
connection: metrics
|
|
sql: "SELECT value FROM metrics WHERE key = 'queue' ORDER BY ts DESC LIMIT 1"
|
|
refresh: 300ms
|
|
stale_time: 150ms
|
|
|
|
filters: {}
|
|
|
|
sections:
|
|
- id: live
|
|
title: "Live"
|
|
widgets:
|
|
- id: cpu
|
|
type: kpi
|
|
title: "CPU %"
|
|
query: cpu_current
|
|
mapping: { value: "value" }
|
|
span: 4
|
|
|
|
- id: mem
|
|
type: kpi
|
|
title: "Memory %"
|
|
query: memory_current
|
|
mapping: { value: "value" }
|
|
span: 4
|
|
|
|
- id: queue
|
|
type: kpi
|
|
title: "Queue"
|
|
query: queue_depth
|
|
mapping: { value: "value" }
|
|
span: 4
|
|
|
|
- id: history
|
|
title: "CPU History (60s)"
|
|
widgets:
|
|
- id: cpu_line
|
|
type: line_chart
|
|
title: "CPU % (ultimo minuto)"
|
|
query: cpu_history
|
|
mapping:
|
|
x: "ts"
|
|
y: "value"
|
|
options: { curve: linear, show_grid: true, height: 250 }
|
|
span: 12
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Dashboard con busqueda — filtro texto
|
|
|
|
Dashboard que permite buscar texto con debounce.
|
|
|
|
```yaml
|
|
settings:
|
|
title: "Buscador de funciones"
|
|
refresh: 10s
|
|
columns: 12
|
|
|
|
theme: "dark"
|
|
|
|
connections:
|
|
registry:
|
|
driver: sqlite
|
|
path: ../../../registry.db
|
|
|
|
queries:
|
|
search_results:
|
|
connection: registry
|
|
sql: |
|
|
SELECT id, kind, lang, domain, description
|
|
FROM functions
|
|
WHERE id LIKE '%' || :q || '%'
|
|
OR description LIKE '%' || :q || '%'
|
|
ORDER BY updated_at DESC
|
|
LIMIT 50
|
|
refresh: 10s
|
|
params:
|
|
q: "$filter.busqueda"
|
|
|
|
result_count:
|
|
connection: registry
|
|
sql: |
|
|
SELECT COUNT(*) as value
|
|
FROM functions
|
|
WHERE id LIKE '%' || :q || '%'
|
|
OR description LIKE '%' || :q || '%'
|
|
refresh: 10s
|
|
params:
|
|
q: "$filter.busqueda"
|
|
|
|
filters:
|
|
busqueda:
|
|
type: text
|
|
label: "Buscar"
|
|
default: ""
|
|
placeholder: "nombre o descripcion..."
|
|
debounce: 300
|
|
|
|
sections:
|
|
- id: results
|
|
title: "Resultados"
|
|
widgets:
|
|
- id: count
|
|
type: kpi
|
|
title: "Coincidencias"
|
|
query: result_count
|
|
mapping: { value: "value" }
|
|
span: 3
|
|
|
|
- id: results_table
|
|
type: table
|
|
title: "Funciones encontradas"
|
|
query: search_results
|
|
mapping:
|
|
columns:
|
|
- { key: "id", label: "ID" }
|
|
- { key: "kind", label: "Kind" }
|
|
- { key: "lang", label: "Lang" }
|
|
- { key: "domain", label: "Domain" }
|
|
- { key: "description", label: "Descripcion" }
|
|
span: 12
|
|
```
|
|
|
|
---
|
|
|
|
## Patrones comunes
|
|
|
|
### Una query alimenta varios widgets
|
|
|
|
```yaml
|
|
queries:
|
|
summary:
|
|
sql: "SELECT SUM(amount) as revenue, COUNT(*) as orders, AVG(amount) as avg FROM orders"
|
|
|
|
sections:
|
|
- id: kpis
|
|
title: "KPIs"
|
|
widgets:
|
|
- id: rev
|
|
type: kpi
|
|
query: summary
|
|
mapping: { value: "revenue", format: "$,.2f" }
|
|
span: 4
|
|
- id: ord
|
|
type: kpi
|
|
query: summary
|
|
mapping: { value: "orders", format: "," }
|
|
span: 4
|
|
- id: avg
|
|
type: kpi
|
|
query: summary
|
|
mapping: { value: "avg", format: "$,.2f" }
|
|
span: 4
|
|
```
|
|
|
|
La query se ejecuta una sola vez y los 3 KPIs leen del mismo resultado.
|
|
|
|
### Seccion colapsable para detalle
|
|
|
|
```yaml
|
|
sections:
|
|
- id: detail
|
|
title: "Detalle (click para expandir)"
|
|
collapsible: true
|
|
widgets:
|
|
- id: log_table
|
|
type: table
|
|
query: logs
|
|
span: 12
|
|
```
|
|
|
|
### Grid flexible por seccion
|
|
|
|
```yaml
|
|
sections:
|
|
- id: kpis
|
|
title: "KPIs"
|
|
# hereda columns: 12 del global → 4 KPIs de span 3
|
|
widgets:
|
|
- { id: a, type: kpi, query: q, mapping: { value: v }, span: 3 }
|
|
- { id: b, type: kpi, query: q, mapping: { value: v }, span: 3 }
|
|
- { id: c, type: kpi, query: q, mapping: { value: v }, span: 3 }
|
|
- { id: d, type: kpi, query: q, mapping: { value: v }, span: 3 }
|
|
|
|
- id: charts
|
|
title: "Graficos"
|
|
columns: 2 # override: solo 2 columnas
|
|
widgets:
|
|
- { id: chart1, type: line_chart, query: q1, mapping: { x: a, y: b }, span: 1 }
|
|
- { id: chart2, type: bar_chart, query: q2, mapping: { x: a, y: b }, span: 1 }
|
|
|
|
- id: full
|
|
title: "Tabla"
|
|
columns: 1 # 1 columna = ancho completo
|
|
widgets:
|
|
- { id: tbl, type: table, query: q3, span: 1 }
|
|
```
|