📊 Progetto

M365 License Dashboard

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.

PowerShell Graph API Azure Functions Azure Static Web Apps Azure Table Storage Azure DevOps Chart.js OAuth 2.0
Screenshot M365 License Dashboard

Il problema

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.

La soluzione

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.

Come funziona

Browser (dashboard HTML)
│ [non autenticato] → redirect /.auth/login/aad
│ [autenticato] → accesso alla dashboard

Azure Static Web Apps (Easy Auth — Entra ID)
│ fetch → /api/GetM365Data

Azure Function (PowerShell HTTP trigger)
│ OAuth2 client credentials → access token

Microsoft Graph API
│ GET /organization · /subscribedSkus · /users

Azure Function elabora i dati
│ upsert snapshot mese corrente ──► Azure Table Storage
│ GET ultimi 12 mesi di storico ◄──────────────────────

Browser renderizza la dashboard

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.

Cosa mostra la dashboard

La dashboard è organizzata in sezioni distinte, ognuna pensata per rispondere a una domanda specifica.

📊
KPI globali
Licenze acquistate, assegnate, disponibili e percentuale di utilizzo per ogni piano, con formattazione numerica italiana.
🍩
Grafici per SKU
Donut chart selezionabile per ogni licenza — o per tutte aggregate — con dettaglio assegnate vs disponibili e anelli di saturazione colorati.
👤
Assegnazioni dirette e miste
Elenco utenti con licenze assegnate direttamente o in modo misto (diretta + gruppo), con filtro per tipo, per SKU e ricerca real-time su nome e UPN.
📈
Trend mensile storico
Grafico lineare degli ultimi 12 mesi per singola SKU o per tutte aggregate, con dati persistiti su Azure Table Storage ad ogni refresh.
😴
Utenti inattivi e disabilitati
Lista utenti senza accesso interattivo da ≥ 45 giorni con licenze a pagamento + account disabilitati con licenze ancora assegnate (badge DISABILITATO). Per ogni utente vengono mostrati separatamente accesso interattivo e non-interattivo — la soglia si applica solo al primo.
🚨
Alert consolidati
Tab con tutte le criticità ordinate per priorità: licenze in esaurimento, assegnazioni dirette, utenti misti, account disabilitati con licenze attive (CRIT) e utenti inattivi (INFO).
🏛️
Governance Score
Punteggio 0–100 calcolato in tempo reale in base a percentuale di assegnazioni dirette, miste e utenti inattivi — relativo alla dimensione del tenant. Include raccomandazioni dinamiche prioritizzate.
📅
Confronto mese precedente
Delta colorati ▲▼ sui KPI principali e sul Governance Score rispetto al mese precedente. Lo snapshot è persistente in localStorage — sopravvive alla chiusura del browser.
📥
Export CSV & PDF
Export CSV su ogni sezione dati (licenze, utenti, inattivi, alert, report governance) con BOM UTF-8 per Excel. Export PDF completo dell'intera dashboard con un click, grafici inclusi.
🧪
Modalità Demo
Toggle "Demo" nell'header che popola l'intera dashboard con dati simulati realistici — tenant "Contoso Italia", 6 SKU con stati variati, 30 utenti, 11 inattivi, Governance Score 71. Nessun dato reale esposto, nessuna scrittura in localStorage.
🔍
Filtro licenze visibili
Pannello filtro nell'header con toggle per ogni SKU — scegli quali licenze includere nel calcolo. Il filtro si applica a tutti i grafici, KPI, alert, Governance Score e tabelle, ed è persistente in 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.

Stack tecnico

Il progetto è volutamente minimalista lato dipendenze — tutto quello che serve è già nell'ecosistema Azure e Microsoft.

Frontend

HTML / CSS / JavaScript Chart.js (self-hosted) Azure Static Web Apps

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.

CI/CD

GitHub Actions Azure Pipelines Azure Repos

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.

Backend

Azure Functions (PowerShell 7.4) Consumption plan OAuth 2.0 Client Credentials Azure Table Storage

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.

Autenticazione

Microsoft Entra ID App Registration Application Permissions Admin Consent Easy Auth (Azure SWA)

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.

PowerShell — autenticazione Graph API
# Recupero token OAuth2 con client credentials $body = @{ grant_type = "client_credentials" client_id = $env:CLIENT_ID client_secret = $env:CLIENT_SECRET scope = "https://graph.microsoft.com/.default" } $token = (Invoke-RestMethod ` "https://login.microsoftonline.com/$env:TENANT_ID/oauth2/v2.0/token" ` -Method POST ` -Body $body).access_token # Lettura licenze del tenant $skus = (Invoke-RestMethod ` "https://graph.microsoft.com/v1.0/subscribedSkus" ` -Headers @{ Authorization = "Bearer $token" }).value

Cosa ho imparato

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:

Sviluppi futuri

Il progetto è funzionante e in continua evoluzione. I miglioramenti ancora in cantiere:

Prova la dashboard

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.