Una tarjeta de Azure Monitor para una métrica concreta en tu dashboard

  • Imagen de redactorDaniel J. Saldaña
  • 24 de octubre de 2026
Una tarjeta de Azure Monitor para una métrica concreta en tu dashboard

Un dashboard de infraestructura no tiene por qué arrancar con veinte gráficas. Una tarjeta bien elegida ya puede responder algo valioso:

¿Cómo ha evolucionado la CPU de esta máquina virtual durante la última hora?

Ese es el tamaño de la integración de hoy. Partimos de una sola métrica de Azure Monitor, la consultamos desde el backend y devolvemos al frontend una serie temporal pequeña y fácil de representar.

Tarjeta del dashboard
↓
API privada autorizada
↓
Azure Monitor Metrics API
↓
Último valor, agregación y puntos para la gráfica

El mismo patrón sirve para solicitudes por segundo, latencia, disponibilidad, transacciones de base de datos o mensajes en cola. Lo que cambia es la métrica permitida para cada tipo de recurso.

Escoge una pregunta antes de escoger la métrica

Una tarjeta útil combina tres decisiones explícitas:

Decisión Ejemplo
Recurso Una máquina virtual concreta
Métrica Percentage CPU
Ventana y agregación Última hora, media cada cinco minutos

No todas las métricas existen para todos los proveedores, y los nombres exactos importan. Por eso no es buena idea aceptar metricName libremente desde la URL. Configura las métricas que tu producto soporta o recupéralas de las definiciones de Azure Monitor en una acción administrativa controlada.

El límite de seguridad

El navegador solicita «la gráfica de esta tarjeta», no un identificador ARM arbitrario. El backend resuelve el recurso desde su inventario, comprueba que el usuario puede verlo y usa una identidad administrada con permisos de lectura de monitorización, por ejemplo el rol Monitoring Reader en el alcance adecuado.

Browser ── sesión ──► API de la aplicación ── identidad administrada ──► Azure Monitor
│
└── resourceId obtenido de un registro autorizado

Además de proteger los tokens, este diseño evita que un usuario use el dashboard para explorar métricas de recursos que pertenecen a otro equipo o suscripción.

Consulta una serie temporal desde TypeScript

El endpoint de métricas se encuentra bajo el resourceId del recurso. Este ejemplo encapsula la llamada y transforma la respuesta de Azure en un contrato mínimo:

import { DefaultAzureCredential } from '@azure/identity';
const credential = new DefaultAzureCredential();
const scope = 'https://management.azure.com/.default';
export type MetricPoint = {
timestamp: string;
value: number | null;
};
export type MetricSeries = {
name: string;
aggregation: string;
unit?: string;
points: MetricPoint[];
};
export async function queryMetric(input: {
resourceId: string;
metricName: string;
aggregation: 'Average' | 'Total' | 'Maximum' | 'Minimum' | 'Count';
from: Date;
to: Date;
interval?: string;
}): Promise<MetricSeries> {
if (!input.resourceId.startsWith('/subscriptions/')) {
throw new Error('Azure resource id no válido');
}
const token = await credential.getToken(scope);
if (!token) throw new Error('No se pudo obtener token de Azure');
const url = new URL(`https://management.azure.com${input.resourceId}/providers/Microsoft.Insights/metrics`);
url.searchParams.set('api-version', '2023-10-01');
url.searchParams.set('metricnames', input.metricName);
url.searchParams.set('aggregation', input.aggregation);
url.searchParams.set('timespan', `${input.from.toISOString()}/${input.to.toISOString()}`);
url.searchParams.set('interval', input.interval ?? 'PT5M');
const response = await fetch(url, {
headers: { Authorization: `Bearer ${token.token}` },
signal: AbortSignal.timeout(8_000),
});
if (!response.ok) {
throw new Error(`Azure Monitor devolvió ${response.status}`);
}
const body = await response.json();
const metric = body.value?.[0];
const data = metric?.timeseries?.[0]?.data ?? [];
const aggregationKey = input.aggregation.toLowerCase();
return {
name: metric?.name?.value ?? input.metricName,
aggregation: input.aggregation,
unit: metric?.unit,
points: data.map((point: Record<string, unknown>) => ({
timestamp: String(point.timeStamp),
value: typeof point[aggregationKey] === 'number' ? point[aggregationKey] : null,
})),
};
}

La respuesta de Azure puede contener varios timeseries si aplicas dimensiones. Para la primera tarjeta es mejor elegir una sola serie o añadir una dimensión explícita desde el servidor. Devolver todas sin una decisión de producto suele acabar en una gráfica ilegible.

Convierte una tarjeta de dashboard en una consulta limitada

La interfaz puede mandar el identificador de una tarjeta, no los parámetros de ARM:

const metricCards = {
vmCpu: {
metricName: 'Percentage CPU',
aggregation: 'Average' as const,
interval: 'PT5M',
},
};
export async function GET({ locals, params }: ApiContext) {
const user = await requireUser(locals);
const card = await dashboardCards.findById(params.cardId);
if (!card || !(await canViewCard(user, card))) {
return Response.json({ error: 'No encontrado' }, { status: 404 });
}
const definition = metricCards[card.metricKey as keyof typeof metricCards];
if (!definition) {
return Response.json({ error: 'Métrica no soportada' }, { status: 422 });
}
const now = new Date();
const result = await metricsCache.getOrSet(
`metric:${card.id}:${Math.floor(now.getTime() / 60_000)}`,
() =>
queryMetric({
resourceId: card.azureResourceId,
...definition,
from: new Date(now.getTime() - 60 * 60_000),
to: now,
}),
60_000
);
return Response.json(result);
}

La caché de corta duración amortigua una navegación con varias tarjetas abiertas y reduce el riesgo de llegar a límites de servicio. Nunca cachees un token; cachea únicamente el resultado normalizado y durante un intervalo visible para la persona usuaria.

Una interfaz que no exagera el dato

En el frontend, enseña el último valor junto con la agregación y la ventana:

const last = series.points.at(-1)?.value;
<section aria-label={`${series.name}, última hora`}>
<p>{series.name}</p>
<strong>{last == null ? 'Sin datos' : `${last.toFixed(1)} ${series.unit ?? ''}`}</strong>
<small>{series.aggregation} · últimos 60 minutos</small>
<Sparkline points={series.points} />
</section>;

Hay tres detalles que suelen mejorar mucho la lectura:

  1. Muestra Sin datos si el último punto es nulo; no lo conviertas en cero.
  2. Indica unidad, agregación y periodo. Un 48 sin contexto no comunica nada.
  3. Diferencia la visualización de una alerta. Una gráfica roja no sustituye una regla de alerta operativa.

Trata los errores de Azure como estados de producto

Respuesta Qué mostrar
Sin datos en la ventana Tarjeta vacía con periodo y opción de ampliar
403 «No hay permiso para consultar la métrica», con registro técnico interno
429 o timeout Último dato cacheado y reintento con espera
Métrica no soportada No ofrecer la tarjeta para ese tipo de recurso
Error de tu backend Un error técnico, no una alerta de Azure

Los límites, la granularidad disponible y las dimensiones dependen del tipo de recurso. Empieza con una ventana corta y una agregación conocida; cuando el caso esté validado, añade selector de periodo, comparación y filtros de dimensión.

Preparado para crecer

Una vez resuelta una tarjeta, conservar el contrato evita que cada gráfica sea una integración distinta:

type MetricSeries = {
name: string;
aggregation: string;
unit?: string;
points: Array<{ timestamp: string; value: number | null }>;
};

Puedes alimentar este mismo DTO desde Azure Monitor, desde una caché de series o desde un servicio de agregación. El componente sigue siendo el mismo.

La referencia de Metrics - List de Azure Monitor documenta los parámetros de la llamada. Con una sola métrica bien protegida, un dashboard ya gana una señal de operación accionable sin convertirse en una réplica completa del portal de Azure.

¡Suscríbete y recibe actualizaciones sobre tecnología, diseño, productividad, programación y mucho más!
0