Bladeren bron

Aggiunte nuove pagine html

Fabio Antonelli 7 maanden geleden
bovenliggende
commit
f9582fef52
3 gewijzigde bestanden met toevoegingen van 1150 en 0 verwijderingen
  1. 193 0
      Backend/endpoints.py
  2. 587 0
      Backend/templates/ADMIN.html
  3. 370 0
      Backend/templates/mappa_login.html

+ 193 - 0
Backend/endpoints.py

@@ -0,0 +1,193 @@
+from fastapi import FastAPI, Form, Request, Depends, HTTPException, Body
+from pydantic import BaseModel
+from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse
+from fastapi.templating import Jinja2Templates
+from fastapi.staticfiles import StaticFiles
+from fastapi.middleware.cors import CORSMiddleware
+from starlette.middleware.sessions import SessionMiddleware
+import secrets
+import uvicorn
+import logging
+import requests  # Import the requests library here
+from typing import Annotated, Dict, List, Optional #import typing
+
+
+# --- Keycloak Configuration ---
+KEYCLOAK_SERVER_URL = "http://165.22.75.145:8080"  # Double-check this! Changed to http
+KEYCLOAK_REALM = "Generation"
+KEYCLOAK_CLIENT_ID = "web-app-pw"
+KEYCLOAK_CLIENT_SECRET = "fQGWt8HSPn65cCKTOzE5FigqZhf8QTYW"
+KEYCLOAK_SCOPE = "codicefiscale email openid ruolo"
+
+# --- App Initialization ---
+app = FastAPI()
+
+# --- Logging Configuration ---
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+# --- CORS ---
+# origins = [
+#     "http://localhost:8000/*",
+#     "http://localhost:3000/*",
+# ]
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+# --- Session ---
+app.add_middleware(SessionMiddleware, secret_key=secrets.token_hex(32))
+
+# --- Helper Functions (modified to be part of the API) ---
+
+# Function to get tokens from keycloak
+def get_token_from_keycloak(username, password) -> Dict:
+    """Retrieves access and refresh tokens from Keycloak."""
+    url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token"
+    payload = f'grant_type=password&client_id={KEYCLOAK_CLIENT_ID}&scope={KEYCLOAK_SCOPE}&username={username}&password={password}&client_secret={KEYCLOAK_CLIENT_SECRET}'
+    headers = {
+        'Content-Type': 'application/x-www-form-urlencoded'
+    }
+    try:
+        response = requests.post(url, headers=headers, data=payload, timeout=10)
+        response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
+        return response.json()
+    except requests.exceptions.RequestException as e:
+        logger.error(f"Error getting token from Keycloak: {e}")
+        raise HTTPException(status_code=500, detail=f"Failed to get token from Keycloak: {e}") from e
+
+def refresh_token_from_keycloak(refresh_token: str) -> Dict:
+    """Refreshes the access token using the refresh token."""
+    url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token"
+    payload = f'grant_type=refresh_token&client_id={KEYCLOAK_CLIENT_ID}&client_secret={KEYCLOAK_CLIENT_SECRET}&refresh_token={refresh_token}'
+    headers = {
+        'Content-Type': 'application/x-www-form-urlencoded'
+    }
+    try:
+        response = requests.post(url, headers=headers, data=payload, timeout=10)
+        response.raise_for_status()
+        return response.json()
+    except requests.exceptions.RequestException as e:
+        logger.error(f"Error refreshing token: {e}")
+        raise HTTPException(status_code=500, detail=f"Failed to refresh token: {e}") from e
+
+
+def introspect_keycloak_token_request(access_token: str) -> Dict:
+    """Introspects a Keycloak token to verify if it's active."""
+    url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token/introspect"
+    payload = f'token={access_token}&client_id={KEYCLOAK_CLIENT_ID}&client_secret={KEYCLOAK_CLIENT_SECRET}'
+    headers = {
+        'Content-Type': 'application/x-www-form-urlencoded'
+    }
+    try:
+        response = requests.post(url, headers=headers, data=payload, verify=False, timeout=10)
+        response.raise_for_status()
+        return response.json()
+    except requests.exceptions.RequestException as e:
+        logger.error(f"Error introspecting token: {e}")
+        raise HTTPException(status_code=500, detail=f"Failed to introspect token: {e}") from e
+
+def get_user_info_from_keycloak(access_token: str) -> Dict:
+    """Gets user information from Keycloak using the access token."""
+    url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/userinfo"
+    headers = {
+        'Authorization': f'Bearer {access_token}'
+    }
+    try:
+        response = requests.get(url, headers=headers, timeout=10)
+        response.raise_for_status()
+        return response.json()
+    except requests.exceptions.RequestException as e:
+        logger.error(f"Error getting user info: {e}")
+        raise HTTPException(status_code=500, detail=f"Failed to get user info: {e}") from e
+
+def logout_keycloak(refresh_token:str):
+    url = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/logout"
+
+    payload = f'refresh_token={refresh_token}&client_id={KEYCLOAK_CLIENT_ID}&client_secret={KEYCLOAK_CLIENT_SECRET}'
+    headers = {
+    'Content-Type': 'application/x-www-form-urlencoded'
+    }
+    try:
+        response = requests.request("POST", url, headers=headers, data=payload)
+        response.raise_for_status()
+        return response.json()
+    except requests.exceptions.RequestException as e:
+        logger.error(f"Error logging out user: {e}")
+        raise HTTPException(status_code=500, detail=f"Failed to logout user: {e}") from e
+
+class Credenziali(BaseModel):
+    username: str
+    password: str
+
+app.mount("/static", StaticFiles(directory="static"), name="static")
+templates = Jinja2Templates(directory="templates")
+
+
+@app.get("/callback")
+async def callback(request: Request):
+    return templates.TemplateResponse("mappa_logout.html", context={"request": request, "title": "Mappa"})
+
+@app.get("/access")
+@app.post("/access")
+async def login(request: Request, username: Optional[str] = Form(None), password: Optional[str] = Form(None)):
+    error = None
+    if request.method == "POST":
+        try:
+            request.session["user_info"] = {}
+            #print(request.session.get("tokens"))
+            
+            tokens = get_token_from_keycloak(username, password)#request.body.json()["username"], request.json()["password"])
+            
+            # Save tokens and information to the session
+
+            request.session["access_token"] = tokens["access_token"]
+            request.session["refresh_token"] = tokens["refresh_token"]
+
+            #print(tokens["access_token"])
+
+            # Get user info and save it to the session too
+
+            user_info = get_user_info_from_keycloak(tokens["access_token"])
+
+            request.session["user_info"] = user_info
+
+
+
+            return RedirectResponse(url=f"/callback?access_token={tokens["access_token"]}&refresh_token={tokens["refresh_token"]}", status_code=303)
+
+        except HTTPException as e:
+
+            return JSONResponse(content={"detail": e.detail}, status_code=e.status_code)
+
+        except Exception as e:
+
+            logger.error(f"An unexpected error occurred during login: {e}")
+
+            return JSONResponse(content={"detail": "An unexpected error occurred"}, status_code=500)
+    
+    if request.session.get("access_token") is not None:
+                #print(request.session.get("access_token"))
+                if introspect_keycloak_token_request(str(request.session.get("access_token")))["active"] == True:
+                    #print(3)
+                    return RedirectResponse(url="/callback", status_code=303)
+                try:
+                    tokens = refresh_token_from_keycloak(str(request.session.get("refresh_token")))
+
+                    #localStorage.setItem('jwtToken', tuoJWT);
+                    request.session["access_token"] = tokens["access_token"]
+                    request.session["refresh_token"] = tokens["refresh_token"]
+
+                    return RedirectResponse(url=f"/callback?access_token={tokens["access_token"]}&refresh_token={tokens["refresh_token"]}", status_code=303)
+
+                except Exception as e:
+
+                    logger.error(f"An unexpected error occurred during login: {e}")
+
+                    return JSONResponse(content={"detail": "An unexpected error occurred"}, status_code=500)
+                
+    return templates.TemplateResponse("login.html", {"request": request, "error": error})

+ 587 - 0
Backend/templates/ADMIN.html

@@ -0,0 +1,587 @@
+<!DOCTYPE html>
+<html lang="it">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Gestione Dati Immobiliari</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap-italia@2.13.4/dist/css/bootstrap-italia.min.css"
+        rel="stylesheet">
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
+    <style>
+        /* Stile per la riga blu scuro */
+        .top-bar {
+            background-color: #004080;
+            /* Blu scuro */
+            color: white;
+            padding: 0.5rem 0;
+            text-align: left;
+            /* Allineamento a sinistra */
+            font-size: 0.9rem;
+        }
+
+
+        /* Stile per l'header */
+        .header {
+            background-color: #0066CC;
+            /* Blu chiaro */
+            color: white;
+            padding: 1rem 0;
+        }
+
+        /* Stile per il logo "PW" dentro un cerchio */
+        .logo { 
+            font-family: 'Georgia', serif; 
+            font-size: 1.5rem; 
+            width: 50px; height: 50px; 
+            background-color: white; 
+            color: #0066CC; 
+            border-radius: 50%; 
+            margin-right: 10px; 
+            display: inline-flex; 
+            align-items: center; 
+            justify-content: center; 
+        }
+        /* Stile per il titolo "Segnalazioni e Supporto Tecnico" */
+        .header-title {
+            color: white;
+            font-size: 1.5rem;
+            /* Stessa grandezza di "Segnalazioni e Supporto Tecnico" */
+            font-weight: bold;
+            display: inline-block;
+            vertical-align: middle;
+        }
+
+        /* Stile per il contenuto principale */
+        .main-content {
+            background-color: #f8f9fa;
+            /* Grigio chiaro */
+            padding: 2rem 0;
+        }
+
+        /* Stile per il banner di conferma */
+        .banner-confirm {
+            display: none;
+            /* Nascondi il banner inizialmente */
+            background-color: #28a745;
+            color: white;
+            padding: 1rem;
+            margin-top: 1rem;
+            border-radius: 4px;
+            text-align: center;
+            transition: opacity 0.5s ease-in-out;
+            /* Transizione per la dissolvenza */
+            opacity: 1;
+            /* Inizialmente visibile */
+        }
+
+        /* Aggiungi margine ai lati del contenitore principale */
+        .container {
+            padding-left: 2rem;
+            padding-right: 2rem;
+        }
+        .it-footer-main {
+            background-color: #004D99;
+            padding-left: 2rem;
+            padding-right: 2rem;
+        }
+        /* Aggiungi margine ai lati del footer */
+        .it-footer-main .container {
+            background-color: #004D99;
+            padding-left: 2rem;
+            padding-right: 2rem;
+        }
+
+        /* Stile per i link con sottolineatura solo al passaggio del mouse */
+        .link-underline-hover {
+            text-decoration: none;
+            /* Rimuove la sottolineatura di base */
+        }
+
+        .link-underline-hover:hover {
+            text-decoration: underline;
+            /* Aggiunge la sottolineatura solo al passaggio del mouse */
+        }
+
+        /* Stile per il footer */
+        .data-table {
+            margin-top: 20px;
+        }
+
+        .filter-section {
+            margin-bottom: 20px;
+        }
+
+        .pagination {
+            margin-top: 20px;
+        }
+        .form-modifica *  {
+            font-size: 12px;
+        }
+    </style>
+</head>
+
+<body>
+    <!-- Riga blu scuro con scritto "Project Work" allineata a sinistra -->
+    <div class="top-bar">
+        <div class="container">
+            Project Work
+        </div>
+    </div>
+    <!-- Header  è troppo stretto segue la linea della pagina  -->
+    <header class="header">
+        <div class="container">
+            <div class="row align-items-center">
+                <div class="col d-flex">
+                    <!-- Logo "PW" dentro un cerchio, ora cliccabile -->
+                    <a href="/callback" class="logo-link">
+                        <div class="logo">PW</div>
+                    </a>
+                    <h1 class="header-title">Gestione Dati Immobiliari</h1>
+                    <!-- Titolo "Mappa" con la stessa grandezza -->
+                </div>
+                <div class="col-auto d-flex align-items-center">
+                    <a href="/admin" class="text-white me-3 link-underline-hover">Gestione Dati Immobiliari</a>
+                    <a href="/tfo" class="text-white me-3 link-underline-hover">TFO</a>
+                    <a href="/buildings" class="text-white me-3 link-underline-hover">Registrazione degli Edifici</a>
+                    <a href="/mappa_login" class="btn btn-light">Logout</a>
+                </div>
+            </div>
+        </div>
+    </header>
+    <div class="container mt-4">
+        <div class="filter-section">
+            <div class="row mt-3 mb-3">
+                <!-- Filtro per codice utente (solo per amministratore) -->
+                <div class="col-md-4 flex-column gap-4" id="userFilterSection" style="display: none;">
+                    <label for="userFilter">Filtra per Codice Utente</label>
+                    <input type="text" class="form-control" id="userFilter" placeholder="Inserisci codice utente">
+                </div>
+                <!-- Filtro per indirizzo -->
+                <div class="col-md-4 d-flex flex-column gap-4">
+                    <label for="addressFilter">Filtra per Indirizzo</label>
+                    <input type="text" class="form-control" id="addressFilter" placeholder="Inserisci indirizzo">
+                </div>
+                <!-- Filtro per data -->
+                <div class="col-md-4 d-flex flex-column gap-4">
+                    <label for="dateFilter">Filtra per Data</label>
+                    <input type="date" class="form-control" id="dateFilter">
+                </div>
+            </div>
+            <div class="container mt-4">
+                <div class="filter-section">
+                    <div class="row mt-3 mb-3">
+                        </div>
+                    <button class="btn btn-primary" onclick="filtraDati()">Applica Filtri</button>
+                    <button class="btn btn-secondary" onclick="azzeraFiltri()">Azzera Filtri</button> </div>
+            
+                </div>
+        </div>
+
+        <table class="table data-table">
+            <thead>
+                <tr>
+                    <th>ID Utente</th>
+                    <th>Indirizzo</th>
+                    <th>Latitudine</th>
+                    <th>Longitudine</th>
+                    <th>Codice Catastale</th>
+                    <th>Data Predisposizione</th>
+                    <th>Azioni</th>
+                </tr>
+            </thead>
+            <tbody id="dataTableBody">
+                <!-- Dati verranno caricati qui dinamicamente -->
+            </tbody>
+        </table>
+
+        <!-- Paginazione -->
+        <nav aria-label="Paginazione">
+            <ul class="pagination" id="pagination">
+                <!-- Pulsanti di paginazione verranno aggiunti qui dinamicamente -->
+            </ul>
+        </nav>
+    </div>
+    <!-- Widget ElevenLabs ConvAI -->
+    <elevenlabs-convai agent-id="GlBWa9xJ6GdD7bAve6Yq"></elevenlabs-convai>
+    <script src="https://elevenlabs.io/convai-widget/index.js" async type="text/javascript"></script>
+    <script>
+        document.addEventListener('DOMContentLoaded', function() {
+            // Ottieni l'URL corrente
+            const urlParams = new URLSearchParams(window.location.search);
+            // Estrai il token dal parametro di query "token"
+            const ruolo = urlParams.get('ruolo');
+            const codice_fiscale = urlParams.get('codice_fiscale');
+            // Salva il token in localStorage
+            if (ruolo && codice_fiscale) {
+                localStorage.setItem('ruolo', ruolo);
+                localStorage.setItem('codice_fiscale', codice_fiscale);
+                // Rimuovi il token dall'URL per sicurezza (opzionale)
+                window.history.replaceState({}, document.title, window.location.pathname);
+                //window.location.href = window.location.pathname;
+                //Puoi anche reindirizzare ad una pagina pulita.
+                //window.location.href = '/altra-pagina';
+            }
+            
+        });
+    </script>
+    <!-- Footer -->
+    <footer id="footer" class="it-footer bg-black" role="contentinfo">
+        <div class="it-footer-main py-3">
+            <div class="container">
+                <section class="py-4">
+                    <div class="row">
+                        <div class="col-lg-3 col-md-3 col-sm-6 pb-2">
+                            <div class="link-list-wrapper">
+                                <h2 class="h5">Esplora SINFI</h2>
+                                <ul id="footer-menu-col-1" class="link-list">
+                                    <li id="menu-item-sinfi-1" class="menu-item">
+                                        <a class="list-item" href="https://sinfi.it/portal/sinfi-menu/che-cose/">Cos'è
+                                            SINFI</a>
+                                    </li>
+                                    <li id="menu-item-sinfi-2" class="menu-item">
+                                        <a class="list-item" href="https://sinfi.it/realms/master/protocol/openid-connect/auth?client_id=sinfi_user_service&response_type=code&redirect_uri=https://sinfi.it/sinfi_gateway/labs_keycloak/post_login/&scope=email&state=">Infrastrutture Registrate</a>
+                                    </li>
+                                    <li id="menu-item-sinfi-3" class="menu-item">
+                                        <a class="list-item"
+                                            href="https://sinfi.it/portal/sinfi-menu/riferimenti-normativi/">Normative
+                                            di Riferimento</a>
+                                    </li>
+                                    <li id="menu-item-sinfi-4" class="menu-item">
+                                        <a class="list-item" href="https://sinfi.it/realms/master/protocol/openid-connect/auth?client_id=sinfi_user_service&response_type=code&redirect_uri=https://sinfi.it/sinfi_gateway/labs_keycloak/post_login/&scope=email&state=">Open Data SINFI</a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                        <div class="col-lg-3 col-md-3 col-sm-6 pb-2">
+                            <div class="link-list-wrapper">
+                                <h2 class="h5">Aiuto e Supporto</h2>
+                                <ul id="footer-menu-col-2" class="link-list">
+                                    <li id="menu-item-aiuto-1" class="menu-item">
+                                        <a class="list-item" href="/instructions">Istruzioni</a>
+                                    </li>
+                                    <li id="menu-item-aiuto-2" class="menu-item">
+                                        <a class="list-item" href="/faq">FAQ - Domande Frequenti</a>
+                                    </li>
+                                    <li id="menu-item-aiuto-3" class="menu-item">
+                                        <a class="list-item" href="/techsup">Segnalazioni e Supporto Tecnico</a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                        <!-- Colonna "Community" con i loghi dei social -->
+                        <div class="col-lg-3 col-md-3 col-sm-6 pb-2">
+                            <div class="link-list-wrapper">
+                                <h2 class="h5">Community</h2>
+                                <div class="social-icons">
+                                    <a href="https://www.linkedin.com" target="_blank" class="me-3"><i
+                                            class="fab fa-linkedin"></i></a>
+                                    <a href="https://www.youtube.com" target="_blank" class="me-3"><i
+                                            class="fab fa-youtube"></i></a>
+                                    <a href="https://www.facebook.com" target="_blank" class="me-3"><i
+                                            class="fab fa-facebook"></i></a>
+                                    <a href="https://www.instagram.com" target="_blank" class="me-3"><i
+                                            class="fab fa-instagram"></i></a>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="col-lg-3 col-md-3 col-sm-6 pb-2">
+                            <div class="link-list-wrapper">
+                                <h2 class="h5">SINFI</h2>
+                                <ul id="footer-menu-col-4" class="link-list">
+                                    <li id="menu-item-377" class="menu-item">
+                                        <a target="_blank" href="https://sinfi.it/portal/">Vai al sito ufficiale
+                                            SINFI</a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
+                </section>
+            </div>
+        </div>
+        <div class="it-footer-small-prints clearfix">
+            <div class="container">
+                <nav class="menu-footer-menu-ita-container" aria-label="link utili">
+                    <ul id="menu-footer-menu-ita"
+                        class="it-footer-small-prints-list list-inline mb-0 d-flex flex-column flex-md-row">
+                        <li id="menu-item-413" class="menu-item list-inline-item"><a href="/media-policy/">Media
+                                Policy</a></li>
+                        <li id="menu-item-453" class="menu-item list-inline-item"><a href="/note-legali/">Privacy Policy
+                                &amp; Note Legali</a></li>
+                        <li id="menu-item-1310" class="menu-item list-inline-item"><a target="_blank"
+                                href="https://form.agid.gov.it/view/9df3de50-7a42-11ef-8989-9dcab5eaa914">Dichiarazione
+                                di accessibilità</a></li>
+                        <li id="menu-item-411" class="menu-item list-inline-item"><a href="/mappa-del-sito/">Mappa del
+                                sito</a></li>
+                        <li id="menu-item-1277" class="menu-item list-inline-item"><a href="/open-data-spid/">Open Data
+                                SPID</a></li>
+                    </ul>
+                </nav>
+            </div>
+        </div>
+    </footer>
+
+
+
+    <script>
+        // Dati di esempio (simulazione di un database)
+        const dati = [
+            { idUtente: 1, indirizzo: "Via Roma 1", latitudine: "41.9028", longitudine: "12.4964", codiceCatastale: "A123", dataPredisposizione: "2023-10-01" },
+            { idUtente: 2, indirizzo: "Via Milano 2", latitudine: "45.4642", longitudine: "9.1900", codiceCatastale: "B456", dataPredisposizione: "2023-10-02" },
+            { idUtente: 1, indirizzo: "Via Napoli 3", latitudine: "40.8522", longitudine: "14.2681", codiceCatastale: "C789", dataPredisposizione: "2023-10-03" },
+            { idUtente: 3, indirizzo: "Via Torino 4", latitudine: "45.0703", longitudine: "7.6869", codiceCatastale: "D012", dataPredisposizione: "2023-10-04" },
+            { idUtente: 2, indirizzo: "Via Firenze 5", latitudine: "43.7696", longitudine: "11.2558", codiceCatastale: "E345", dataPredisposizione: "2023-10-05" },
+            { idUtente: 1, indirizzo: "Via Venezia 6", latitudine: "45.4408", longitudine: "12.3155", codiceCatastale: "F678", dataPredisposizione: "2023-10-06" },
+        ];
+
+        // Simulazione del ruolo utente (1 = operatore, 2 = amministratore)
+        const ruoloUtente = 2; // Cambia questo valore per testare
+        const idUtenteCorrente = 1; // ID dell'utente corrente (solo per operatori)
+
+        // Mostra/nascondi il filtro per codice utente in base al ruolo
+        if (ruoloUtente === 2) {
+            document.getElementById('userFilterSection').style.display = 'flex';
+        }
+
+        // Variabili per la paginazione
+        const righePerPagina = 3; // Numero di righe da visualizzare per pagina
+        let paginaCorrente = 1;
+
+        // Funzione per filtrare i dati
+        function filtraDati() {
+            const userFilter = document.getElementById('userFilter').value.toLowerCase();
+            const addressFilter = document.getElementById('addressFilter').value.toLowerCase();
+            const dateFilter = document.getElementById('dateFilter').value;
+
+            const datiFiltrati = dati.filter(item => {
+                const matchUser = ruoloUtente === 2 ? item.idUtente.toString().includes(userFilter) : item.idUtente === idUtenteCorrente;
+                const matchAddress = item.indirizzo.toLowerCase().includes(addressFilter);
+                const matchDate = dateFilter ? item.dataPredisposizione === dateFilter : true;
+
+                return matchUser && matchAddress && matchDate;
+            });
+
+            caricaDatiInTabella(datiFiltrati);
+            aggiornaPaginazione(datiFiltrati);
+        }
+
+        // Funzione per caricare i dati nella tabella
+        function caricaDatiInTabella(dati) {
+            const tbody = document.getElementById('dataTableBody');
+            tbody.innerHTML = ''; // Svuota la tabella
+
+            const inizio = (paginaCorrente - 1) * righePerPagina;
+            const fine = inizio + righePerPagina;
+            const datiPagina = dati.slice(inizio, fine);
+
+            datiPagina.forEach(item => {
+                const row = `
+                    <tr>
+                        <td>${item.idUtente}</td>
+                        <td>${item.indirizzo}</td>
+                        <td>${item.latitudine}</td>
+                        <td>${item.longitudine}</td>
+                        <td>${item.codiceCatastale}</td>
+                        <td>${item.dataPredisposizione}</td>
+                        <td>
+                            <button class="btn btn-sm btn-primary" onclick="modificaDato(${item.idUtente})"><i class="fas fa-edit"></i></button>
+                            <button class="btn btn-sm btn-danger" onclick="cancellaDato(${item.idUtente})"><i class="fas fa-trash"></i></button>
+                        </td>
+                    </tr>
+                `;
+                tbody.innerHTML += row;
+            });
+        }
+
+        // Funzione per aggiornare la paginazione
+        function aggiornaPaginazione(dati) {
+            const pagination = document.getElementById('pagination');
+            pagination.innerHTML = ''; // Svuota la paginazione
+
+            const totalePagine = Math.ceil(dati.length / righePerPagina);
+
+            // Pulsante "Precedente"
+            pagination.innerHTML += `
+                <li class="page-item ${paginaCorrente === 1 ? 'disabled' : ''}">
+                    <a class="page-link" href="#" onclick="cambiaPagina(${paginaCorrente - 1})">Precedente</a>
+                </li>
+            `;
+
+            // Numeri di pagina
+            for (let i = 1; i <= totalePagine; i++) {
+                pagination.innerHTML += `
+                    <li class="page-item ${i === paginaCorrente ? 'active' : ''}">
+                        <a class="page-link" href="#" onclick="cambiaPagina(${i})">${i}</a>
+                    </li>
+                `;
+            }
+
+            // Pulsante "Successivo"
+            pagination.innerHTML += `
+                <li class="page-item ${paginaCorrente === totalePagine ? 'disabled' : ''}">
+                    <a class="page-link" href="#" onclick="cambiaPagina(${paginaCorrente + 1})">Successivo</a>
+                </li>
+            `;
+        }
+
+        // Funzione per cambiare pagina
+        function cambiaPagina(nuovaPagina) {
+            paginaCorrente = nuovaPagina;
+            filtraDati();
+        }
+
+        // Funzioni di esempio per modifica e cancellazione
+        function modificaDato(idUtente) {
+    const riga = document.querySelector(`tr[data-id="${idUtente}"]`);
+    if (!riga) return;
+
+    const celle = riga.cells;
+    const dati = {
+        idUtente: celle[0].textContent,
+        indirizzo: celle[1].textContent,
+        latitudine: celle[2].textContent,
+        longitudine: celle[3].textContent,
+        codiceCatastale: celle[4].textContent,
+        dataPredisposizione: celle[5].textContent,
+    };
+
+    const formModifica = `
+        <td colspan="6">
+            <form class="form-modifica d-flex">
+                <div class="row flex-nowrap">
+                    <div class="col-md-2 d-flex flex-column gap-1">
+                        <label for="modificaIdUtente">Id</label>
+                        <input type="text" class="form-control" id="modificaIdUtente" ${ruoloUtente === 2 ? '' : 'disabled'} value="${dati.idUtente}">
+                    </div>
+                    <div class="col-md-2 d-flex flex-column gap-1">
+                        <label for="modificaIndirizzo">Indirizzo</label>
+                        <input type="text" class="form-control" id="modificaIndirizzo" value="${dati.indirizzo}">
+                    </div>
+                    <div class="col-md-2 d-flex flex-column gap-1">
+                        <label for="modificaLatitudine">Latitudine</label>
+                        <input type="text" class="form-control" id="modificaLatitudine" value="${dati.latitudine}">
+                    </div>
+                    <div class="col-md-2 d-flex flex-column gap-1">
+                        <label for="modificaLongitudine">Longitudine</label>
+                        <input type="text" class="form-control" id="modificaLongitudine" value="${dati.longitudine}">
+                    </div>
+                    <div class="col-md-2 d-flex flex-column gap-1">
+                        <label for="modificaCodiceCatastale">Codice Catastale</label>
+                        <input type="text" class="form-control" id="modificaCodiceCatastale" value="${dati.codiceCatastale}">
+                    </div>
+                    <div class="col-md-2 d-flex flex-column gap-1">
+                        <label for="modificaData">Data Predisposizione</label>
+                        <input type="date" class="form-control" id="modificaData" value="${dati.dataPredisposizione}">
+                    </div>
+                    <div class="text-right col-md-1">
+                        <button type="button" class="btn btn-success btn-sm" onclick="confermaModifica(${idUtente})">Conferma</button>
+                    </div>
+                </div>
+            </form>
+        </td>
+    `;
+
+    riga.innerHTML = formModifica;
+}
+        // Funzione per confermare la modifica
+        function confermaModifica(idUtente) {
+    // Trova la riga della tabella corrispondente all'ID utente
+    const riga = document.querySelector(`tr[data-id="${idUtente}"]`);
+    if (!riga) return;
+
+    let NewidUtente = idUtente;
+    // Recupera i valori modificati dal form
+    if (ruoloUtente === 2) {
+        NewidUtente = document.getElementById('modificaIdUtente').value;
+    }
+
+    const indirizzo = document.getElementById('modificaIndirizzo').value;
+    const latitudine = document.getElementById('modificaLatitudine').value;
+    const longitudine = document.getElementById('modificaLongitudine').value;
+    const codiceCatastale = document.getElementById('modificaCodiceCatastale').value;
+    const dataPredisposizione = document.getElementById('modificaData').value;
+
+    // Aggiorna l'array `dati` con i nuovi valori
+    const indice = dati.findIndex(item => item.idUtente === idUtente);
+    if (indice !== -1) {
+        dati[indice] = {
+            idUtente: parseInt(NewidUtente),
+            indirizzo,
+            latitudine,
+            longitudine,
+            codiceCatastale,
+            dataPredisposizione
+        };
+    }
+
+    // Ricarica la tabella per riflettere le modifiche
+    filtraDati();
+}
+// Funzione per azzerare i filtri
+function azzeraFiltri() {
+    document.getElementById('userFilter').value = '';
+    document.getElementById('addressFilter').value = '';
+    document.getElementById('dateFilter').value = '';
+    paginaCorrente = 1; // Resetta la pagina corrente
+    filtraDati(); // Ricarica i dati senza filtri
+}
+function caricaDatiInTabella(dati) {
+    const tbody = document.getElementById('dataTableBody');
+    tbody.innerHTML = ''; // Svuota la tabella
+
+    const inizio = (paginaCorrente - 1) * righePerPagina;
+    const fine = inizio + righePerPagina;
+    const datiPagina = dati.slice(inizio, fine);
+
+    datiPagina.forEach(item => {
+        const row = `
+            <tr data-id="${item.idUtente}">
+                <td>${item.idUtente}</td>
+                <td>${item.indirizzo}</td>
+                <td>${item.latitudine}</td>
+                <td>${item.longitudine}</td>
+                <td>${item.codiceCatastale}</td>
+                <td>${item.dataPredisposizione}</td>
+                <td>
+                    <button class="btn btn-sm btn-primary" onclick="modificaDato(${item.idUtente})"><i class="fas fa-edit"></i></button>
+                    <button class="btn btn-sm btn-danger" onclick="cancellaDato(${item.idUtente})"><i class="fas fa-trash"></i></button>
+                </td>
+            </tr>
+        `;
+        tbody.innerHTML += row;
+    });
+}
+        // Funzione per cancellare un dato
+        function cancellaDato(idUtente) {
+            if (confirm("Sei sicuro di voler eliminare questo dato?")) {
+                // L'utente ha confermato l'eliminazione
+                // 1. Rimuovi la riga dalla tabella
+                const riga = document.querySelector(`tr[data-id="${idUtente}"]`);
+                if (riga) {
+                    riga.remove();
+                }
+
+                // 2. Aggiorna l'array 'dati' rimuovendo l'elemento corrispondente
+                const indice = dati.findIndex(item => item.idUtente === idUtente);
+                if (indice !== -1) {
+                    dati.splice(indice, 1);
+                }
+
+                // 3. Aggiorna la paginazione e la visualizzazione della tabella
+                aggiornaPaginazione(dati);
+                caricaDatiInTabella(dati);
+                alert(`Cancella dato per utente ${idUtente}`);
+            } else {
+                // L'utente ha annullato l'eliminazione
+                alert("Eliminazione annullata.");
+            }
+        }
+
+        // Carica i dati iniziali
+        filtraDati();
+    </script>
+    
+</body>
+
+</html>

+ 370 - 0
Backend/templates/mappa_login.html

@@ -0,0 +1,370 @@
+<!DOCTYPE html>
+<html lang="it">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Mappa Interattiva con Layer Colorati</title>
+    
+    <!-- Fogli di stile -->
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-italia@2.13.4/dist/css/bootstrap-italia.min.css">
+    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css">
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
+    
+    <style>
+        .top-bar { background-color: #004080; color: white; padding: 0.5rem 0; }
+        .header { background-color: #0066CC; color: white; padding: 1rem 0; }
+        /* Stile per il logo "PW" dentro un cerchio */
+        .logo { 
+            font-family: 'Georgia', serif; 
+            font-size: 1.5rem; 
+            width: 50px; height: 50px; 
+            background-color: white; 
+            color: #0066CC; 
+            border-radius: 50%; 
+            margin-right: 10px; 
+            display: inline-flex; 
+            align-items: center; 
+            justify-content: center; 
+        }
+        .header-title { color: white; font-size: 1.5rem; font-weight: bold; }
+        .main-content { background-color: #f8f9fa; padding: 2rem 0; }
+        #map { height: 500px; width: 100%; border-radius: 8px; }
+        .search-container { margin: 20px 0; }
+        .search-box { display: flex; gap: 10px; max-width: 600px; margin: 0 auto; }
+        .coordinate-box { background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-top: 20px; }
+    </style>
+</head>
+<body>
+    <!-- Header -->
+    <div class="top-bar">
+        <div class="container">Project Work</div>
+    </div>
+
+     <!-- Header -->
+     <header class="header">
+        <div class="container">
+            <div class="row align-items-center">
+                <div class="col d-flex">
+                    <!-- Logo "PW" dentro un cerchio, ora cliccabile -->
+                    <a href="/access" class="logo-link">
+                        <div class="logo">PW</div>
+                    </a>
+                    <h1 class="header-title">Mappa</h1> <!-- Titolo "Mappa" con la stessa grandezza -->
+                </div>
+                <div class="col-auto">
+                    <a href="/access" class="btn btn-light">Login</a>
+                </div>
+            </div>
+        </div>
+    </header>
+
+    <main class="main-content">
+        <div class="container">
+            <!-- Barra di ricerca -->
+            <div class="search-container">
+                <div class="search-box">
+                    <input type="text" 
+                           id="address" 
+                           class="form-control" 
+                           placeholder="Inserisci l'indirizzo"
+                           style="flex-grow: 1;">
+                    <button id="searchBtn" class="btn btn-primary">
+                        <i class="fas fa-search"></i> Cerca
+                    </button>
+                </div>
+            </div>
+
+            <!-- Mappa -->
+            <div id="map"></div>
+
+            <!-- Dati geografici -->
+            <div class="coordinate-box">
+                <div class="row">
+                    <div class="col-md-4 mb-3">
+                        <label class="form-label">Latitudine:</label>
+                        <input type="number" id="latInput" class="form-control" readonly>
+                    </div>
+                    <div class="col-md-4 mb-3">
+                        <label class="form-label">Longitudine:</label>
+                        <input type="number" id="lngInput" class="form-control" readonly>
+                    </div>
+                    <div class="col-md-4 mb-3">
+                        <label class="form-label">Indirizzo:</label>
+                        <input type="text" id="addressInput" class="form-control" readonly>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </main>
+
+    <!-- Footer -->
+    <footer id="footer" class="it-footer bg-black" role="contentinfo">
+        <!-- Contenuto footer mantenuto uguale -->
+    </footer>
+
+    <!-- Script -->
+    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
+    <script>
+        // Inizializzazione Mappa
+        const map = L.map('map').setView([41.9028, 12.4964], 13);
+        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
+
+        // Layer edifici colorati
+        const edificiLayer = L.layerGroup().addTo(map);
+        let currentMarker = null;
+
+        // Funzione principale per caricare gli edifici
+        async function caricaEdifici() {
+            if (map.getZoom() >= 16) {
+                const bounds = map.getBounds();
+                const bbox = `${bounds.getSouthWest().lat},${bounds.getSouthWest().lng},${bounds.getNorthEast().lat},${bounds.getNorthEast().lng}`;
+                
+                const query = `[out:json];
+                    (
+                        way["building"](${bbox});
+                        relation["building"](${bbox});
+                    );
+                    out geom;
+                    out tags;`;
+
+                try {
+                    const response = await fetch(`https://overpass-api.de/api/interpreter?data=${encodeURIComponent(query)}`);
+                    const data = await response.json();
+
+                    edificiLayer.clearLayers();
+                    
+                    data.elements.forEach(element => {
+                        if (element.type === 'way' && element.geometry) {
+                            const latlngs = element.geometry.map(coord => [coord.lat, coord.lon]);
+                            var statoEdificio = false;
+                            let colorePoligono ;
+                            if (statoEdificio) colorePoligono = 'yellow';
+                            else colorePoligono = 'green';
+                            
+                            // const colori = ['#FF0000', '#00FF00'];
+                            const polygon = L.polygon(latlngs, {
+                                fillColor: colorePoligono,
+                                color: colorePoligono,
+                                fillOpacity: 0.5
+                            }).addTo(edificiLayer);
+
+                            polygon.on('click', async function() {
+                                var buildingType = element.tags && element.tags.building ? element.tags.building : 'Sconosciuto';
+                                try {
+                                    await getAddress(latlngs[0][0], latlngs[0][1]);
+                                    var popupContent = `
+                                        <b>Coordinate:</b> ${latlngs[0][0]}, ${latlngs[0][1]}<br>
+                                        <b>Tipo edificio:</b> ${buildingType}<br>
+                                        <b>Indirizzo:</b> ${document.getElementById('addressInput').value}<br>
+                                        <b>Stato:</b> ${statoEdificio}<br>
+                                        <b>Codice Catastale:</b> nessuno
+                                    `;
+                                    polygon.bindPopup(popupContent).openPopup();
+                                } catch (error) {
+                                    console.error("Errore nel recupero dell'indirizzo:", error);
+                                    var popupContent = `
+                                        <b>Coordinate:</b> ${latlngs[0][0]}, ${latlngs[0][1]}<br>
+                                        <b>Tipo edificio:</b> ${buildingType}<br>
+                                        <b>Indirizzo:</b> Indirizzo non disponibile<br>
+                                        <b>Stato:</b> ${statoEdificio}<br>
+                                        <b>Codice Catastale:</b> nessuno
+                                    `;
+                                    polygon.bindPopup(popupContent).openPopup();
+                                }
+                            });
+                        }
+                    });
+                } catch (error) {
+                    console.error("Errore nel caricamento edifici:", error);
+                }
+            } else {
+                edificiLayer.clearLayers();
+            }
+        }
+        async function getAddress(lat, lng) {
+            console.log(typeof(lat));
+            console.log(typeof(lng));
+
+            try {
+                const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lng}`);
+                const data = await response.json();
+
+                if (data.address) {
+                    document.getElementById('addressInput').value = data.display_name;
+                } else {
+                    document.getElementById('addressInput').value = "Indirizzo non trovato";
+                }
+            } catch (error) {
+                console.error("Errore nel geocoding inverso:", error);
+                document.getElementById('addressInput').value = "Errore nel geocoding inverso";
+            }
+        }
+
+        // Funzioni di gestione posizione
+        async function updatePosition(lat, lng) {
+            try {
+                const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`);
+                const data = await response.json();
+                
+                document.getElementById('latInput').value = lat;
+                document.getElementById('lngInput').value = lng;
+                document.getElementById('addressInput').value = data.display_name || "Indirizzo non disponibile";
+                
+                if (currentMarker) map.removeLayer(currentMarker);
+                currentMarker = L.marker([lat, lng]).addTo(map);
+                
+            } catch (error) {
+                console.error("Errore di geocoding inverso:", error);
+            }
+        }
+
+        // Ricerca indirizzo
+        async function searchAddress() {
+            const address = document.getElementById('address').value;
+            if (!address) return;
+
+            try {
+                const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}`);
+                const data = await response.json();
+                
+                if (data.length === 0) {
+                    alert("Indirizzo non trovato");
+                    return;
+                }
+
+                const { lat, lon: lng } = data[0];
+                map.setView([lat, lng], 18);
+                updatePosition(lat, lng);
+                
+            } catch (error) {
+                console.error("Errore di ricerca:", error);
+            }
+        }
+
+        // Event listeners
+        document.getElementById('searchBtn').addEventListener('click', searchAddress);
+        document.getElementById('address').addEventListener('keypress', (e) => {
+            if (e.key === 'Enter') searchAddress();
+        });
+
+        map.on('click', (e) => {
+            const { lat, lng } = e.latlng;
+            updatePosition(lat, lng);
+        });
+
+        map.on('moveend', caricaEdifici);
+        map.on('zoomend', caricaEdifici);
+        caricaEdifici();
+    </script>
+
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap-italia@2.8.1/dist/js/bootstrap-italia.bundle.min.js"></script>
+
+   
+    <script>
+        document.addEventListener('DOMContentLoaded', function() {
+            // Ottieni l'URL corrente
+            const urlParams = new URLSearchParams(window.location.search);
+            // Estrai il token dal parametro di query "token"
+            const ruolo = urlParams.get('ruolo');
+            const codice_fiscale = urlParams.get('codice_fiscale');
+            // Salva il token in localStorage
+            if (ruolo && codice_fiscale) {
+                localStorage.setItem('ruolo', ruolo);
+                localStorage.setItem('codice_fiscale', codice_fiscale);
+                // Rimuovi il token dall'URL per sicurezza (opzionale)
+                window.history.replaceState({}, document.title, window.location.pathname);
+                //window.location.href = window.location.pathname;
+                //Puoi anche reindirizzare ad una pagina pulita.
+                //window.location.href = '/altra-pagina';
+            }
+            
+        });
+    </script>
+    <!-- Widget ElevenLabs ConvAI -->
+    <elevenlabs-convai agent-id="GlBWa9xJ6GdD7bAve6Yq"></elevenlabs-convai>
+    <script src="https://elevenlabs.io/convai-widget/index.js" async type="text/javascript"></script>
+
+    <footer id="footer" class="it-footer bg-black" role="contentinfo">
+        <div class="it-footer-main py-3">
+            <div class="container">
+                <section class="py-4">
+                    <div class="row">
+                        <div class="col-lg-3 col-md-3 col-sm-6 pb-2">
+                            <div class="link-list-wrapper">
+                                <h2 class="h5">Esplora SINFI</h2>
+                                <ul id="footer-menu-col-1" class="link-list">
+                                    <li id="menu-item-sinfi-1" class="menu-item">
+                                        <a class="list-item" href="https://sinfi.it/portal/sinfi-menu/che-cose/">Cos'è SINFI</a>
+                                    </li>
+                                    <li id="menu-item-sinfi-2" class="menu-item">
+                                        <a class="list-item" href="https://sinfi.it/realms/master/protocol/openid-connect/auth?client_id=sinfi_user_service&response_type=code&redirect_uri=https://sinfi.it/sinfi_gateway/labs_keycloak/post_login/&scope=email&state=">Infrastrutture Registrate</a>
+                                    </li>
+                                    <li id="menu-item-sinfi-3" class="menu-item">
+                                        <a class="list-item" href="https://sinfi.it/portal/sinfi-menu/riferimenti-normativi/">Normative di Riferimento</a>
+                                    </li>
+                                    <li id="menu-item-sinfi-4" class="menu-item">
+                                        <a class="list-item" href="https://sinfi.it/realms/master/protocol/openid-connect/auth?client_id=sinfi_user_service&response_type=code&redirect_uri=https://sinfi.it/sinfi_gateway/labs_keycloak/post_login/&scope=email&state=">Open Data SINFI</a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                        <div class="col-lg-3 col-md-3 col-sm-6 pb-2">
+                            <div class="link-list-wrapper">
+                                <h2 class="h5">Aiuto e Supporto</h2>
+                                <ul id="footer-menu-col-2" class="link-list">
+                                    <li id="menu-item-aiuto-1" class="menu-item">
+                                        <a class="list-item" href="/instructions">Istruzioni</a>
+                                    </li>
+                                    <li id="menu-item-aiuto-2" class="menu-item">
+                                        <a class="list-item" href="/faq">FAQ - Domande Frequenti</a>
+                                    </li>
+                                    <li id="menu-item-aiuto-3" class="menu-item">
+                                        <a class="list-item"href="/techsup">Segnalazioni e Supporto Tecnico</a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                        <!-- Colonna "Community" con i loghi dei social -->
+                        <div class="col-lg-3 col-md-3 col-sm-6 pb-2">
+                            <div class="link-list-wrapper">
+                                <h2 class="h5">Community</h2>
+                                <div class="social-icons">
+                                    <a href="https://www.linkedin.com" target="_blank" class="me-3"><i class="fab fa-linkedin"></i></a>
+                                    <a href="https://www.youtube.com" target="_blank" class="me-3"><i class="fab fa-youtube"></i></a>
+                                    <a href="https://www.facebook.com" target="_blank" class="me-3"><i class="fab fa-facebook"></i></a>
+                                    <a href="https://www.instagram.com" target="_blank" class="me-3"><i class="fab fa-instagram"></i></a>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="col-lg-3 col-md-3 col-sm-6 pb-2">
+                            <div class="link-list-wrapper">
+                                <h2 class="h5">SINFI</h2>
+                                <ul id="footer-menu-col-4" class="link-list">
+                                    <li id="menu-item-377" class="menu-item">
+                                        <a target="_blank" href="https://sinfi.it/portal/">Vai al sito ufficiale SINFI</a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
+                </section>
+            </div>
+        </div>
+        <div class="it-footer-small-prints clearfix">
+            <div class="container">
+                <nav class="menu-footer-menu-ita-container" aria-label="link utili">
+                    <ul id="menu-footer-menu-ita" class="it-footer-small-prints-list list-inline mb-0 d-flex flex-column flex-md-row">
+                        <li id="menu-item-413" class="menu-item list-inline-item"><a href="/media-policy/">Media Policy</a></li>
+                        <li id="menu-item-453" class="menu-item list-inline-item"><a href="/note-legali/">Privacy Policy &amp; Note Legali</a></li>
+                        <li id="menu-item-1310" class="menu-item list-inline-item"><a target="_blank" href="https://form.agid.gov.it/view/9df3de50-7a42-11ef-8989-9dcab5eaa914">Dichiarazione di accessibilità</a></li>
+                        <li id="menu-item-411" class="menu-item list-inline-item"><a href="/mappa-del-sito/">Mappa del sito</a></li>
+                        <li id="menu-item-1277" class="menu-item list-inline-item"><a href="/open-data-spid/">Open Data SPID</a></li>
+                    </ul>
+                </nav>
+            </div>
+        </div>
+    </footer>
+</body>
+</html>