Come ho costruito una dashboard per monitorare la salute dei servizi Microsoft 365
Da sysadmin M365, una delle situazioni più fastidiose è scoprire un problema di servizio dopo che gli utenti te lo hanno già segnalato. Exchange Online rallenta, Teams non carica i file, SharePoint restituisce errori: il primo riflesso è aprire il portale Microsoft 365 Admin Center, navigare fino a Service Health, e aspettare che la pagina carichi. Se sei fortunato, trovi già un incident aperto. Se non lo sei, cominci a investigare in autonomia perdendo tempo prezioso.
Questo ciclo mi ha convinto che valesse la pena costruire qualcosa di meglio: una dashboard dedicata, sempre aperta, che mostrasse lo stato dei servizi M365 in tempo reale senza dover aprire il portale ogni volta.
Il problema reale
Il Service Health del portale Admin Center è funzionale, ma non è pensato per il monitoraggio continuo. È sepolto tra diverse voci di menu, non si aggiorna automaticamente, e non dà immediatamente una visione d’insieme.
Ma il problema non è solo di velocità — è di contesto. Quando un utente apre un ticket perché “Outlook non funziona”, il sysadmin deve capire in fretta se è un problema locale, un misconfiguration, o un incident lato Microsoft. Senza una vista immediata sullo stato dei servizi, si parte sempre dall’ipotesi peggiore: si investiga come se fosse un problema interno, si coinvolgono colleghi, si aprono sessioni remote. Poi, venti minuti dopo, si scopre che Microsoft ha pubblicato un advisory su Exchange Online che spiega tutto. Venti minuti buttati, moltiplicati per ogni ticket dello stesso tipo.
Quello che volevo era semplice: una pagina web, sempre aperta su un monitor secondario o su un tab fisso, che mi mostrasse a colpo d’occhio se qualcosa stava andando storto. Con i dettagli degli incident attivi, gli aggiornamenti Microsoft in tempo reale, e la possibilità di filtrare per servizio o classificazione.
Perché non usare le alternative esistenti
Prima di costruire qualcosa da zero, ho valutato le opzioni già disponibili.
Il portale Admin Center è la fonte ufficiale, ma come detto non è pensato per il monitoraggio passivo. Richiede login, navigazione, e non si aggiorna in tempo reale. Non puoi tenerlo aperto su un monitor e aspettarti di notare un cambiamento a colpo d’occhio.
Le email di notifica di Service Health arrivano, ma con un ritardo variabile e finiscono nel rumore della inbox. In un ambiente dove ricevi decine di email al giorno da portali, sistemi di ticketing e alert di sicurezza, una mail di Service Health si perde facilmente.
Soluzioni di terze parti (StatusPage aggregator, tool di monitoring SaaS) esistono, ma introducono una dipendenza esterna, spesso a pagamento, e richiedono di condividere credenziali o token con un servizio esterno. Per un tool interno di monitoraggio operativo, preferisco mantenere tutto dentro il perimetro Azure/M365.
La dashboard che ho costruito non sostituisce nessuno di questi strumenti — li complementa. È il monitor sempre acceso che ti dice “in questo momento va tutto bene” o “attenzione, c’è un incident su Exchange da 6 giorni”. Un’informazione che cambia il modo in cui affronti la giornata.
L’architettura della soluzione
Microsoft Graph API espone due endpoint precisi per questo scopo: uno per lo stato generale dei servizi (/admin/serviceAnnouncement/healthOverviews) e uno per gli incident attivi e le advisory (/admin/serviceAnnouncement/issues). Tutto quello che serviva era un modo per interrogarli periodicamente e presentare i dati in modo leggibile.
Ho scelto un’architettura serverless su Azure, con tre componenti principali.
Azure Function App (PowerShell) — il cuore della soluzione. Una Function HTTP trigger che, ad ogni chiamata, ottiene un token OAuth2 tramite client credentials flow e interroga i due endpoint Graph in sequenza, restituendo i dati combinati in JSON. Il motivo per cui ho usato una Function come proxy, invece di chiamare Graph direttamente dal browser, è duplice: nascondere le credenziali dell’app dal codice frontend, e avere un singolo punto dove gestire autenticazione e logica di chiamata.
$tokenBody = @{
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
scope = "https://graph.microsoft.com/.default"
}
$tokenResponse = Invoke-RestMethod `
-Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" `
-Method POST `
-Body $tokenBody
$health = (Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews" `
-Headers @{ Authorization = "Bearer $($tokenResponse.access_token)" }).value
Azure Static Web App — ospita il frontend, un singolo file HTML/CSS/JS senza dipendenze esterne né build step. Ho scelto di non usare framework per mantenere la soluzione il più semplice possibile da mantenere: niente npm, niente bundle, niente da aggiornare. La Static Web App si deploya automaticamente da GitHub ad ogni push sul branch main.
Microsoft Entra ID (due App Registration) — una per l’accesso app-only a Graph (client credentials, nessun utente interattivo), una per l’autenticazione degli utenti alla dashboard tramite Easy Auth.
La scelta di separare le due App Registration non è casuale. L’App Registration Graph ha permessi Application-level (ServiceHealth.Read.All, ServiceMessage.Read.All) che non devono mai essere esposti o confusi con il contesto utente. L’App Registration Auth gestisce solo il login e non ha permessi Graph. Questa separazione è una best practice di sicurezza: se un giorno dovessi revocare o ruotare le credenziali di una delle due, l’altra continua a funzionare.
Perché PowerShell e non Node.js o C#
La scelta di PowerShell come runtime per la Function merita una spiegazione. In un contesto sysadmin M365, PowerShell è il linguaggio naturale: è lo stesso che usi per gestire Exchange Online, SharePoint, Entra ID, e tutto il resto dello stack Microsoft. Scrivere la Function in PowerShell significa che qualsiasi sysadmin del team può leggere, capire e modificare il codice senza dover imparare un linguaggio diverso. La manutenibilità in un contesto operativo conta più della performance — e per un proxy che fa due chiamate REST, la performance di PowerShell è più che adeguata.
Perché HTML/CSS/JS e non React o altri servizi
Stessa logica: semplicità e manutenibilità. Un singolo file HTML senza dipendenze esterne significa zero build step, zero vulnerabilità npm da aggiornare, zero breaking change da framework. Lo apri, lo leggi, lo modifichi. Per un tool interno usato da un team tecnico ristretto, è la scelta giusta. Se dovesse scalare a decine di viste e funzionalità, riconsidererei.
I dati di Graph API: più ricchi di quanto sembri
Uno degli aspetti che mi ha sorpreso è la profondità dei dati restituiti dagli endpoint Service Health di Graph API.
L’endpoint healthOverviews non restituisce solo un semaforo verde/rosso per servizio. Ogni entry include lo status dettagliato con valori che distinguono tra serviceDegradation, serviceInterruption, serviceOutage, restoringService, e falsePositive — ognuno con un significato operativo diverso. Nella dashboard, ho mappato questi stati a badge colorati e, per gli stati critici, a un dot animato che attira immediatamente l’attenzione periferica.
L’endpoint issues è ancora più ricco. Ogni incident include:
- Titolo e ID — per tracciamento e correlazione con i ticket interni
- Classificazione —
incident(interruzione con impatto diretto) vsadvisory(degradazione parziale, impatto limitato). Questa distinzione è fondamentale: un advisory su una feature secondaria di SharePoint non richiede la stessa reazione di un incident su Exchange Online - startDateTime e lastModifiedDateTime — permettono di calcolare da quanto tempo l’incident è aperto e quanto è recente l’ultimo aggiornamento Microsoft
- Servizio, feature e featureGroup — granularità che il portale Admin Center mostra, ma che qui arriva strutturata in JSON, pronta per essere filtrata
- Lista dei post/aggiornamenti — la timeline completa con tutti gli update pubblicati da Microsoft, in ordine cronologico. Ogni post include timestamp e contenuto (in HTML, che la dashboard converte in testo leggibile)
Questi dati, combinati in una singola vista, danno al sysadmin un quadro completo che nel portale Admin Center richiede più click e più pagine.
La dashboard: scelte di design con intento operativo
Il frontend non è un esercizio estetico — ogni elemento è pensato per rispondere a una domanda operativa specifica.
La summary bar in alto risponde alla domanda più urgente: “c’è qualcosa che non va, adesso?” Quattro contatori — servizi totali, operativi, degradati, incident aperti — visibili senza scroll, leggibili a tre metri di distanza su un monitor secondario.
La griglia servizi è ordinata per criticità: i servizi degradati appaiono in cima, quelli operativi in fondo. Non serve scorrere 40 card verdi per trovare l’unica gialla. L’ordinamento riflette la priorità operativa: quello che richiede attenzione viene mostrato per primo.
La barra età incident sotto ogni card è un indicatore visivo che risponde a una domanda spesso sottovalutata: “da quanto tempo è aperto questo incident?” Un incident aperto da 2 giorni e uno aperto da 12 richiedono risposte diverse — il secondo probabilmente necessita un workaround, una comunicazione agli utenti, o un’escalation. La barra usa una scala colore progressiva:
| Soglia | Colore | Significato operativo |
|---|---|---|
| ≤ 3 giorni | 🟢 Verde | Incident recente, Microsoft probabilmente sta ancora investigando |
| 4–7 giorni | 🟡 Giallo | In corso da qualche giorno, valuta se comunicare agli utenti |
| 8–14 giorni | 🟠 Arancione | Attenzione — considera workaround o escalation |
| > 14 giorni | 🔴 Rosso | Critico per durata — richiede azione |
La timeline degli aggiornamenti dentro ogni incident mostra i post Microsoft in ordine cronologico inverso (il più recente in cima). I post lunghi sono troncati a 3 righe con un “Mostra tutto” per espanderli. Questo evita che un singolo incident con 15 aggiornamenti occupi l’intero viewport.
I filtri (ricerca, servizio, classificazione) esistono per gli scenari in cui hai già un sospetto: “il problema segnalato dall’utente riguarda Teams — ci sono incident attivi su Teams?” Un filtro rapido risponde in un secondo.
Dark/Light mode non è un vezzo: chi tiene la dashboard aperta su un secondo monitor per ore apprezza la possibilità di ridurre la luminosità. La preferenza è salvata in localStorage e persiste tra le sessioni.
L’autenticazione: Easy Auth con Entra ID
Inizialmente la dashboard era pubblica — accessibile a chiunque avesse l’URL. Accettabile in fase di sviluppo, ma non in produzione. Ho quindi aggiunto un layer di autenticazione tramite Azure Static Web Apps Easy Auth, che integra nativamente Microsoft Entra ID senza richiedere modifiche al codice frontend.
Il funzionamento è trasparente: chiunque apra la dashboard senza una sessione attiva viene reindirizzato automaticamente alla pagina di login Microsoft. Dopo l’autenticazione, viene riportato alla dashboard. Non ho scritto una riga di codice per gestire questo flusso — è tutto configurato tramite un file staticwebapp.config.json nella root della repo:
{
"routes": [
{
"route": "/*",
"allowedRoles": ["authenticated"]
}
],
"responseOverrides": {
"401": {
"redirect": "/.auth/login/aad",
"statusCode": 302
}
}
}
La sezione routes blocca l’accesso a qualsiasi percorso per gli utenti non autenticati. Il responseOverrides converte il 401 in un redirect automatico al login, invece di mostrare una pagina di errore. Il risultato è un flusso pulito: apri la dashboard, se non hai una sessione attiva vai al login Microsoft, torni sulla dashboard autenticato.
L’intera configurazione richiede una seconda App Registration su Entra ID (separata da quella che accede a Graph) e pochi minuti di configurazione sul portale della Static Web App. Nessuna modifica all’HTML.
Un dettaglio importante: al primo accesso, Entra ID mostra una schermata di consenso per i permessi base del profilo. Se un Global Admin o Privileged Role Admin spunta “Acconsenti per conto dell’organizzazione” durante questo primo login, il consenso viene concesso tenant-wide e gli utenti successivi non vedranno mai la schermata. Un passaggio piccolo che migliora significativamente l’esperienza utente.
Limitazioni note e scelte consapevoli
Nessuna soluzione è perfetta, e preferisco essere trasparente su cosa questa dashboard non fa e dove ha margini di miglioramento.
Nessun caching. Ogni apertura della pagina genera due chiamate a Graph API (una per healthOverviews, una per issues). Per un uso individuale o di un team ristretto è irrilevante, ma in scenari con decine di utenti concorrenti potrebbe generare throttling. Un layer di caching con TTL di qualche minuto (Azure Table Storage o Redis) risolverebbe il problema.
Cold start su Consumption plan. La prima invocazione dopo un periodo di inattività ha un ritardo di qualche secondo. Per un tool di monitoraggio non è critico — non è un’API in produzione con SLA di latenza — ma è percepibile. Chi volesse eliminarlo può passare a un piano App Service con Always On abilitato.
Nessuno storico. La dashboard mostra solo lo stato attuale. Gli incident risolti spariscono. Per un tool di monitoraggio real-time è la scelta giusta, ma se servisse uno storico per analisi post-mortem o report, servirebbe un layer di persistenza.
Cosa ho imparato
Costruire questa dashboard mi ha confermato alcune cose che sospettavo già.
Graph API per il Service Health è sorprendentemente ricca: gli endpoint restituiscono non solo lo stato dei servizi, ma anche la timeline completa degli aggiornamenti, i workload impattati, la classificazione degli incident e molto altro. Dati che il portale Admin Center mostra in forma frammentata, qui arrivano tutti in una singola chiamata JSON.
Azure Functions su Consumption plan sono perfette per questo tipo di proxy leggero: costo praticamente zero, zero manutenzione dell’infrastruttura, e il cold start di qualche secondo al primo avvio è assolutamente accettabile per un tool di monitoraggio interno.
Easy Auth è una di quelle funzionalità di Azure che risolve un problema reale con una semplicità disarmante. Aggiungere autenticazione Microsoft a una Static Web App richiede letteralmente una App Registration, una voce di configurazione nel portale e un file JSON nella repo. Per strumenti interni — dove non serve una gestione granulare dei ruoli o flussi di autorizzazione complessi — è la scelta giusta.
La semplicità architetturale paga. Niente framework frontend, niente database, niente container, niente Kubernetes. Tre servizi Azure managed, un file HTML e uno script PowerShell. La soluzione è stata in piedi in meno di un pomeriggio, e da allora non ha richiesto manutenzione se non la rotazione periodica dei client secret. In un contesto operativo dove il tempo è la risorsa più scarsa, una soluzione che funziona e non chiede attenzione è il miglior risultato possibile.
Roadmap
- ✅ Autenticazione utente via Entra ID (Easy Auth su Static Web App)
- 🔲 Caching con TTL configurabile (Azure Table Storage)
- 🔲 Storico incident risolti (ultimi N giorni)
- 🔲 Notifiche automatiche via Teams Webhook su nuovi incident
- 🔲 Esportazione PDF/CSV dello stato corrente
- 🔲 Supporto multi-tenant
Stack tecnologico
Costruito con: Azure Functions · Microsoft Graph API · Azure Static Web Apps · Microsoft Entra ID · PowerShell · HTML/CSS/JS
Considerazioni finali
La dashboard è in uso quotidiano. La tengo aperta su un monitor secondario e in trenta secondi so se c’è qualcosa che non va sui servizi Microsoft — prima che arrivino le segnalazioni degli utenti. Per chi gestisce ambienti M365 con un numero significativo di utenti, avere questa visibilità immediata cambia il modo in cui si reagisce ai problemi: non più reattivo, ma informato. Non è la stessa cosa di essere proattivo — non previeni il problema — ma sai che esiste prima che qualcun altro te lo dica. E in un contesto operativo, questo fa tutta la differenza.
L’architettura è volutamente semplice e replicabile: due App Registration, una Function App, una Static Web App. Niente di esotico, tutto servizio managed Azure. Se gestisci un tenant M365 e vuoi smettere di scoprire i problemi dai ticket degli utenti, questa dashboard è un buon punto di partenza.