Una dashboard web per avere sempre sotto controllo le licenze Microsoft 365 di un tenant enterprise — senza aprire il portale di amministrazione, senza script manuali, senza perdere tempo.
In un tenant Microsoft 365 enterprise le licenze sono una delle voci di costo più rilevanti — e anche una delle più difficili da tenere sotto controllo. Il portale di amministrazione mostra i dati, ma non aggregato, non in tempo reale e non con la profondità necessaria per prendere decisioni rapide.
Domande come "Quante licenze M365 sono assegnate direttamente invece che tramite gruppo?", "Ci sono utenti inattivi da mesi che consumano ancora licenze?" o "Tra quanto saturiamo il piano corrente?" richiedono normalmente query manuali su Graph API o script PowerShell eseguiti all'occorrenza.
Le licenze assegnate direttamente a un utente (invece che tramite gruppo) non vengono revocate automaticamente quando l'account viene disabilitato — continuano a essere conteggiate e fatturate. Rilevare e correggere queste situazioni può tradursi in risparmi concreti.
Ho costruito una dashboard web a singolo file HTML che si connette a Microsoft Graph API tramite un backend serverless su Azure Functions e mostra in tempo reale lo stato completo delle licenze del tenant: utilizzo, assegnazioni, utenti inattivi, trend storico e alert consolidati.
L'obiettivo era avere uno strumento sempre disponibile — accessibile da browser senza installare nulla, senza eseguire script manuali — che fornisse una vista centralizzata e azionabile sulle licenze M365.
Il frontend è un file HTML vanilla con Chart.js per i grafici — nessun framework, nessun build step. Il backend è una Azure Function in PowerShell che autentica l'app tramite OAuth 2.0 con credenziali client (app-only, senza utente interattivo) e interroga Graph API recuperando dati su licenze e utenti. I nomi leggibili delle licenze (es. "Microsoft 365 E3" invece di "ENTERPRISEPACK") vengono mappati in tempo reale scaricando il CSV ufficiale Microsoft. Ad ogni chiamata, la Function salva uno snapshot mensile su Azure Table Storage per alimentare il grafico del trend storico.
La dashboard è organizzata in sezioni distinte, ognuna pensata per rispondere a una domanda specifica.
localStorage — sopravvive alla chiusura del browser.localStorage.localStorage. In modalità Demo il filtro reale viene preservato e ripristinato automaticamente all'uscita.La dashboard include anche dark/light mode, modalità demo con dati simulati per presentazioni (nessun dato reale, nessuna scrittura in localStorage), filtro licenze visibili persistente con isolamento completo dalla modalità demo, refresh automatico all'avvio, stato connessione in tempo reale, risoluzione automatica del nome dei gruppi Entra ID, asset self-hosted (font e Chart.js, nessuna dipendenza CDN esterna), custom dropdown con scroll invisibile e animazione marquee per nomi lunghi, favicon SVG inline nel tab del browser e layout responsive per dispositivi mobili.
Il progetto è volutamente minimalista lato dipendenze — tutto quello che serve è già nell'ecosistema Azure e Microsoft.
Single file HTML senza framework né build tool. Chart.js e i font (Geist Mono, Instrument Sans) sono self-hosted nel repository — nessuna dipendenza da CDN esterni, tutti i file provengono dall'infrastruttura Azure. Il deploy è automatico tramite Azure Static Web Apps, con supporto sia per GitHub Actions che per Azure Pipelines.
Il progetto supporta due modalità di deployment: tramite GitHub + GitHub Actions (setup predefinito) oppure interamente nell'ecosistema Microsoft con Azure Repos e Azure Pipelines — utile per tenant cliente dove i dati devono rimanere all'interno dell'infrastruttura Microsoft.
Una singola Azure Function HTTP trigger in PowerShell agisce da proxy autenticato verso Graph API. L'autenticazione avviene tramite flusso OAuth 2.0 con credenziali client (client ID, secret e tenant ID), senza utente interattivo. Ad ogni invocazione, la Function salva uno snapshot mensile su Azure Table Storage tramite REST API diretta con autenticazione SharedKeyLite — senza moduli PowerShell aggiuntivi. Il piano Consumption garantisce costi vicini a zero per utilizzo normale.
Il progetto utilizza due App Registration distinte su Microsoft Entra ID. La prima gestisce l'accesso backend a Graph API tramite quattro permessi Application: Organization.Read.All, Directory.Read.All, User.Read.All e AuditLog.Read.All, tutti con Admin Consent. La seconda gestisce il login degli utenti tramite Azure Static Web Apps Easy Auth — ogni visitatore deve autenticarsi con un account Microsoft prima di accedere alla dashboard.
Costruire questo progetto da zero mi ha fatto toccare con mano alcuni aspetti dell'ecosistema Microsoft cloud che normalmente si gestiscono solo tramite portale. In particolare:
licenseAssignmentStates per ogni utente e la distinzione tra assegnazione diretta e da gruppo non è immediata da leggere, richiede un po' di parsing.staticwebapp.config.json e App Registration, senza scrivere una riga di codice di autenticazione.Il progetto è funzionante e in continua evoluzione. I miglioramenti ancora in cantiere:
La dashboard è live. Per accedervi è necessario effettuare il login con un account Microsoft — può essere un account aziendale (es. Office 365 / M365) o un account personale Microsoft.
Apri la dashboardÈ richiesto il login con un account Microsoft. La dashboard non è più aperta pubblicamente — l'accesso è protetto tramite Azure Static Web Apps Easy Auth con Entra ID. Una volta autenticati, la dashboard si connette al mio tenant personale e mostra dati reali in tempo reale, con possibilità di passare alla modalità Demo.
Alcune percentuali potrebbero apparire molto basse o a 0% — è del tutto normale.
Il mio tenant include un numero elevato di licenze gratuite fornite da Microsoft, che portano il totale complessivo a oltre un milione di unità. Su quella base, qualsiasi piano a pagamento con poche centinaia di assegnazioni produce inevitabilmente una percentuale trascurabile.
Se volete implementare la dashboard nel vostro tenant per visualizzare i vostri dati reali, contattatemi via email o tramite i social che trovate nel portfolio — sarò felice di aiutarvi.