nis2-agile/public/whistleblowing.html
DevEnv nis2-agile 86e9bdded2 [FEAT] Services API, Webhook, Whistleblowing, Normative + integrazioni
Sprint completo — prodotto presentation-ready:

Services API (read-only, API Key + scope):
- GET /api/services/status|compliance-summary|risks-feed|incidents-feed
- GET /api/services/controls-status|assets-critical|suppliers-risk|policies-approved
- GET /api/services/openapi (spec OpenAPI 3.0.3 JSON)

Webhook Outbound (Stripe-like HMAC-SHA256):
- CRUD api_keys + webhook_subscriptions (Settings → 2 nuovi tab)
- WebhookService: retry 3x backoff (0s/5min/30min), delivery log
- Trigger auto in IncidentController, RiskController, PolicyController
- Delivery log, test ping, processRetry

Nuovi moduli:
- WhistleblowingController (Art.32 NIS2): anonimato garantito, timeline, token tracking
- NormativeController: feed NIS2/ACN/DORA con ACK tracciato per audit

Frontend:
- whistleblowing.html: form submit anonimo/firmato + gestione CISO
- normative.html: feed con presa visione documentata + progress bar ACK
- public/docs/api.html: documentazione API dark theme (Swagger-like)
- settings.html: tab API Keys + tab Webhook
- integrations/: guide per lg231, SustainAI, AllRisk, SIEM (widget + codice)
- Sidebar: Segnalazioni + Normative aggiunte a common.js

DB: migration 007 (api_keys, webhook_subscriptions, webhook_deliveries),
    008 (whistleblowing_reports + timeline),
    009 (normative_updates + normative_ack + seed NIS2/ACN/DORA/ISO)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 13:20:24 +01:00

437 lines
28 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Segnalazioni - NIS2 Agile</title>
<link rel="stylesheet" href="/css/style.css">
<style>
.priority-badge { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 12px; font-size: 0.7rem; font-weight: 700; }
.priority-critical { background: var(--danger-bg); color: var(--danger); }
.priority-high { background: #fff7ed; color: #c2410c; }
.priority-medium { background: var(--warning-bg); color: #a16207; }
.priority-low { background: var(--gray-100); color: var(--gray-600); }
.status-badge { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 12px; font-size: 0.7rem; font-weight: 700; }
.status-received { background: rgba(6,182,212,0.1); color: #0891b2; }
.status-under_review { background: var(--warning-bg); color: #a16207; }
.status-investigating { background: #f3e8ff; color: #7c3aed; }
.status-resolved, .status-closed { background: var(--success-bg); color: var(--success); }
.status-rejected { background: var(--danger-bg); color: var(--danger); }
.anonymous-shield { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; background: rgba(16,185,129,0.1); border-radius: 12px; color: var(--success); font-size: 0.75rem; font-weight: 600; }
.art32-banner { background: linear-gradient(135deg, rgba(6,182,212,0.1), rgba(16,185,129,0.1)); border: 1px solid rgba(6,182,212,0.2); border-radius: var(--border-radius-lg); padding: 20px 24px; margin-bottom: 28px; }
.art32-banner h3 { font-size: 0.9375rem; font-weight: 700; color: var(--gray-800); margin-bottom: 6px; }
.art32-banner p { font-size: 0.8125rem; color: var(--gray-600); }
.track-form { background: var(--gray-50); border: 1px solid var(--gray-200); border-radius: var(--border-radius); padding: 20px; margin-bottom: 24px; }
</style>
</head>
<body>
<div class="app-layout">
<div id="sidebar-container"></div>
<main class="main-content">
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title">Segnalazioni Sicurezza</h1>
<p class="page-subtitle">Canale interno Art.32 NIS2 — Segnala violazioni e anomalie di sicurezza</p>
</div>
<div class="page-header-actions">
<button class="btn btn-secondary" onclick="switchView('track')">
<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/></svg>
Traccia Segnalazione Anonima
</button>
<button class="btn btn-primary" onclick="switchView('submit')">
<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"/></svg>
Nuova Segnalazione
</button>
</div>
</div>
<!-- Art.32 Banner -->
<div class="art32-banner">
<h3>Art. 32 D.Lgs. 138/2024 — Canale Segnalazioni Interno</h3>
<p>Le entità NIS2 devono predisporre canali interni per la segnalazione di violazioni alla sicurezza informatica. Le segnalazioni anonime sono protette e il segnalante non può essere identificato. Ogni segnalazione viene gestita con priorità e tracciabilità.</p>
</div>
<!-- Stats Cards -->
<div class="stats-grid mb-24" id="stats-grid">
<div class="stat-card"><div class="spinner"></div></div>
</div>
<!-- Views -->
<div id="view-list">
<!-- Filters -->
<div class="card mb-16">
<div class="card-body" style="padding:16px 20px;">
<div style="display:flex; gap:12px; flex-wrap:wrap; align-items:center;">
<select id="filter-status" class="form-control" style="width:auto;" onchange="loadReports()">
<option value="">Tutti gli stati</option>
<option value="received">Ricevute</option>
<option value="under_review">In revisione</option>
<option value="investigating">In indagine</option>
<option value="resolved">Risolte</option>
<option value="closed">Chiuse</option>
</select>
<select id="filter-priority" class="form-control" style="width:auto;" onchange="loadReports()">
<option value="">Tutte le priorità</option>
<option value="critical">Critica</option>
<option value="high">Alta</option>
<option value="medium">Media</option>
<option value="low">Bassa</option>
</select>
<select id="filter-category" class="form-control" style="width:auto;" onchange="loadReports()">
<option value="">Tutte le categorie</option>
<option value="security_incident">Incidente sicurezza</option>
<option value="data_breach">Data breach</option>
<option value="unauthorized_access">Accesso non autorizzato</option>
<option value="policy_violation">Violazione policy</option>
<option value="supply_chain_risk">Rischio supply chain</option>
<option value="nis2_non_compliance">Non conformità NIS2</option>
<option value="other">Altro</option>
</select>
</div>
</div>
</div>
<div id="reports-container"><div class="spinner" style="margin:60px auto;"></div></div>
</div>
<!-- Submit Form -->
<div id="view-submit" style="display:none;">
<div class="card" style="max-width:720px;">
<div class="card-header">
<h3>Nuova Segnalazione</h3>
<p style="font-size:0.8125rem; color:var(--gray-500); margin-top:4px;">Segnala anonimamente o con il tuo nome. L'anonimato è garantito dalla piattaforma.</p>
</div>
<div class="card-body">
<div style="margin-bottom:20px; padding:12px 16px; background:rgba(16,185,129,0.08); border:1px solid rgba(16,185,129,0.2); border-radius:var(--border-radius); display:flex; align-items:center; gap:10px;">
<svg viewBox="0 0 20 20" fill="currentColor" width="18" height="18" style="color:var(--success); flex-shrink:0;"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/></svg>
<label style="display:flex; align-items:center; gap:8px; font-size:0.875rem; cursor:pointer;">
<input type="checkbox" id="is-anonymous" checked style="accent-color:var(--success);">
<span><strong>Segnalazione anonima</strong> — La tua identità non verrà registrata</span>
</label>
</div>
<form id="submit-form" onsubmit="submitReport(event)">
<div class="form-group">
<label class="form-label">Categoria *</label>
<select id="rep-category" class="form-control" required>
<option value="">Seleziona categoria...</option>
<option value="security_incident">Incidente di sicurezza</option>
<option value="data_breach">Data breach / Violazione dati</option>
<option value="unauthorized_access">Accesso non autorizzato</option>
<option value="policy_violation">Violazione policy interna</option>
<option value="supply_chain_risk">Rischio supply chain</option>
<option value="nis2_non_compliance">Non conformità NIS2</option>
<option value="corruption">Corruzione / Frode</option>
<option value="other">Altro</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Titolo *</label>
<input type="text" id="rep-title" class="form-control" placeholder="Breve descrizione dell'anomalia..." required>
</div>
<div class="form-group">
<label class="form-label">Descrizione dettagliata *</label>
<textarea id="rep-description" class="form-control" rows="6" placeholder="Descrivi cosa hai osservato, quando è successo, chi è coinvolto (se noto), e qualsiasi altra informazione utile..." required></textarea>
</div>
<div class="form-group">
<label class="form-label">Priorità stimata</label>
<select id="rep-priority" class="form-control">
<option value="medium">Media</option>
<option value="high">Alta</option>
<option value="critical">Critica — Azione immediata richiesta</option>
<option value="low">Bassa</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Articolo NIS2 violato (opzionale)</label>
<input type="text" id="rep-article" class="form-control" placeholder="Es. Art.21, Art.23...">
</div>
<div class="form-group" id="contact-email-group">
<label class="form-label">Email per follow-up (opzionale)</label>
<input type="email" id="rep-email" class="form-control" placeholder="Email solo per ricevere aggiornamenti">
<p style="font-size:0.75rem; color:var(--gray-500); margin-top:4px;">Lascia vuoto per mantenere l'anonimato completo. Oppure fornisci un'email temporanea.</p>
</div>
<div style="display:flex; gap:12px; margin-top:24px;">
<button type="submit" class="btn btn-primary">Invia Segnalazione</button>
<button type="button" class="btn btn-secondary" onclick="switchView('list')">Annulla</button>
</div>
</form>
</div>
</div>
</div>
<!-- Track Anonymous -->
<div id="view-track" style="display:none;">
<div class="card" style="max-width:600px;">
<div class="card-header">
<h3>Traccia Segnalazione Anonima</h3>
<p style="font-size:0.8125rem; color:var(--gray-500); margin-top:4px;">Inserisci il token ricevuto al momento della segnalazione per verificarne lo stato.</p>
</div>
<div class="card-body">
<div class="form-group">
<label class="form-label">Token Segnalazione</label>
<input type="text" id="track-token" class="form-control" placeholder="Inserisci il token...">
</div>
<div style="display:flex; gap:12px;">
<button class="btn btn-primary" onclick="trackReport()">Verifica Stato</button>
<button class="btn btn-secondary" onclick="switchView('list')">Annulla</button>
</div>
<div id="track-result" style="margin-top:24px;"></div>
</div>
</div>
</div>
</main>
</div>
<!-- Report Detail Modal handled by common.js showModal -->
<script src="/js/api.js"></script>
<script src="/js/common.js"></script>
<script src="/js/i18n.js"></script>
<script src="/js/help.js"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
I18n.init();
let currentView = 'list';
const categoryLabels = {
security_incident: 'Incidente sicurezza', data_breach: 'Data breach',
unauthorized_access: 'Accesso non autorizzato', policy_violation: 'Violazione policy',
supply_chain_risk: 'Rischio supply chain', nis2_non_compliance: 'Non conformità NIS2',
corruption: 'Corruzione/Frode', fraud: 'Frode', other: 'Altro'
};
const priorityLabels = { critical: 'Critica', high: 'Alta', medium: 'Media', low: 'Bassa' };
const statusLabels = {
received: 'Ricevuta', under_review: 'In revisione', investigating: 'In indagine',
resolved: 'Risolta', closed: 'Chiusa', rejected: 'Respinta'
};
function switchView(view) {
currentView = view;
['list','submit','track'].forEach(v => {
document.getElementById('view-' + v).style.display = v === view ? 'block' : 'none';
});
if (view === 'list') { loadStats(); loadReports(); }
}
async function loadStats() {
try {
const result = await api.request('GET', '/whistleblowing/stats');
if (result.success) renderStats(result.data.stats);
} catch (e) {}
}
function renderStats(s) {
const grid = document.getElementById('stats-grid');
grid.innerHTML = `
<div class="stat-card">
<div class="stat-label">Totale Segnalazioni</div>
<div class="stat-value">${s.total || 0}</div>
</div>
<div class="stat-card">
<div class="stat-label">Da Gestire</div>
<div class="stat-value" style="color:var(--warning);">${(s.received || 0) + (s.under_review || 0)}</div>
</div>
<div class="stat-card">
<div class="stat-label">In Indagine</div>
<div class="stat-value" style="color:var(--primary);">${s.investigating || 0}</div>
</div>
<div class="stat-card">
<div class="stat-label">Critiche/Alte</div>
<div class="stat-value" style="color:var(--danger);">${(s.critical || 0) + (s.high || 0)}</div>
</div>`;
}
async function loadReports() {
const container = document.getElementById('reports-container');
container.innerHTML = '<div class="spinner" style="margin:60px auto;"></div>';
try {
const params = new URLSearchParams();
const status = document.getElementById('filter-status').value;
const priority = document.getElementById('filter-priority').value;
const category = document.getElementById('filter-category').value;
if (status) params.append('status', status);
if (priority) params.append('priority', priority);
if (category) params.append('category', category);
const result = await api.request('GET', '/whistleblowing/list?' + params);
if (result.success) renderReports(result.data.reports || []);
} catch (e) {
container.innerHTML = '<div class="empty-state"><h4>Errore caricamento segnalazioni</h4></div>';
}
}
function renderReports(reports) {
const container = document.getElementById('reports-container');
if (!reports.length) {
container.innerHTML = `
<div class="empty-state">
<svg viewBox="0 0 20 20" fill="currentColor" width="40" height="40" style="color:var(--gray-300)"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd"/></svg>
<h4>Nessuna segnalazione trovata</h4>
<p>Il canale di segnalazione è attivo. Le segnalazioni appariranno qui.</p>
</div>`;
return;
}
let html = `<div class="table-container"><table>
<thead><tr><th>Codice</th><th>Categoria</th><th>Titolo</th><th>Priorità</th><th>Stato</th><th>Anonima</th><th>Assegnata</th><th>Data</th><th></th></tr></thead><tbody>`;
reports.forEach(r => {
html += `<tr>
<td><code style="font-size:0.8rem;">${escapeHtml(r.report_code)}</code></td>
<td><span class="badge badge-neutral">${escapeHtml(categoryLabels[r.category] || r.category)}</span></td>
<td><strong style="font-size:0.875rem;">${escapeHtml(r.title)}</strong></td>
<td><span class="priority-badge priority-${r.priority}">${escapeHtml(priorityLabels[r.priority] || r.priority)}</span></td>
<td><span class="status-badge status-${r.status}">${escapeHtml(statusLabels[r.status] || r.status)}</span></td>
<td>${r.is_anonymous ? '<span class="anonymous-shield"><svg viewBox="0 0 20 20" fill="currentColor" width="12" height="12"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/></svg>Anonima</span>' : '<span style="color:var(--gray-400); font-size:0.75rem;">Firmata</span>'}</td>
<td>${r.assigned_to_name ? escapeHtml(r.assigned_to_name) : '<span style="color:var(--gray-400);">—</span>'}</td>
<td style="font-size:0.8rem;">${formatDate(r.created_at)}</td>
<td><button class="btn btn-sm btn-secondary" onclick="viewReport(${r.id})">Gestisci</button></td>
</tr>`;
});
html += '</tbody></table></div>';
container.innerHTML = html;
}
async function viewReport(id) {
try {
const result = await api.request('GET', `/whistleblowing/${id}`);
if (!result.success) { showNotification('Errore caricamento.', 'error'); return; }
const r = result.data;
const timelineHtml = (r.timeline || []).map(t => `
<div style="padding:10px 0; border-bottom:1px solid var(--gray-100); display:flex; gap:12px;">
<div style="width:8px; height:8px; border-radius:50%; background:var(--primary); margin-top:6px; flex-shrink:0;"></div>
<div>
<p style="font-size:0.8125rem; color:var(--gray-700);">${escapeHtml(t.description)}</p>
<p style="font-size:0.75rem; color:var(--gray-400);">${formatDateTime(t.created_at)} ${t.created_by_name ? '— ' + escapeHtml(t.created_by_name) : ''}</p>
</div>
</div>`).join('');
showModal(`${escapeHtml(r.report_code)}${escapeHtml(r.title)}`, `
<div style="display:flex; gap:10px; flex-wrap:wrap; margin-bottom:16px;">
<span class="priority-badge priority-${r.priority}">${escapeHtml(priorityLabels[r.priority] || r.priority)}</span>
<span class="status-badge status-${r.status}">${escapeHtml(statusLabels[r.status] || r.status)}</span>
${r.is_anonymous ? '<span class="anonymous-shield"><svg viewBox="0 0 20 20" fill="currentColor" width="12" height="12"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/></svg>Anonima</span>' : ''}
</div>
<div style="padding:12px 16px; background:var(--gray-50); border-radius:var(--border-radius); margin-bottom:16px; font-size:0.875rem; line-height:1.6;">
${escapeHtml(r.description)}
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-bottom:16px; font-size:0.8125rem;">
<div><strong>Categoria:</strong> ${escapeHtml(categoryLabels[r.category] || r.category)}</div>
<div><strong>Art. NIS2:</strong> ${r.nis2_article ? escapeHtml(r.nis2_article) : '—'}</div>
<div><strong>Assegnata a:</strong> ${r.assigned_to_name ? escapeHtml(r.assigned_to_name) : '—'}</div>
<div><strong>Data:</strong> ${formatDateTime(r.created_at)}</div>
</div>
<h4 style="font-size:0.8125rem; font-weight:700; margin-bottom:8px;">Timeline</h4>
<div>${timelineHtml || '<p style="color:var(--gray-400); font-size:0.8rem;">Nessun evento in timeline.</p>'}</div>
<div style="margin-top:20px; display:flex; flex-direction:column; gap:10px;">
<select id="modal-status" class="form-control">
<option value="">Cambia stato...</option>
${['under_review','investigating','resolved','closed','rejected'].map(s =>
`<option value="${s}" ${r.status === s ? 'selected' : ''}>${escapeHtml(statusLabels[s])}</option>`
).join('')}
</select>
<textarea id="modal-notes" class="form-control" rows="3" placeholder="Note di risoluzione...">${escapeHtml(r.resolution_notes || '')}</textarea>
</div>
`, `<button class="btn btn-primary" onclick="updateReport(${id})">Salva</button>
<button class="btn btn-secondary" onclick="closeModal()">Chiudi</button>`);
} catch (e) { showNotification('Errore di connessione.', 'error'); }
}
async function updateReport(id) {
const status = document.getElementById('modal-status').value;
const notes = document.getElementById('modal-notes').value;
const body = {};
if (status) body.status = status;
if (notes) body.resolution_notes = notes;
if (!Object.keys(body).length) { closeModal(); return; }
try {
const result = await api.request('PUT', `/whistleblowing/${id}`, body);
if (result.success) {
closeModal();
showNotification('Segnalazione aggiornata.', 'success');
loadReports(); loadStats();
} else showNotification(result.message || 'Errore.', 'error');
} catch (e) { showNotification('Errore di connessione.', 'error'); }
}
async function submitReport(e) {
e.preventDefault();
const btn = e.target.querySelector('[type=submit]');
btn.disabled = true; btn.textContent = 'Invio...';
try {
const body = {
category: document.getElementById('rep-category').value,
title: document.getElementById('rep-title').value,
description: document.getElementById('rep-description').value,
priority: document.getElementById('rep-priority').value,
nis2_article: document.getElementById('rep-article').value,
is_anonymous: document.getElementById('is-anonymous').checked ? 1 : 0,
contact_email: document.getElementById('rep-email').value,
organization_id: localStorage.getItem('nis2_org_id'),
};
const result = await api.request('POST', '/whistleblowing/submit', body);
if (result.success) {
const token = result.data.anonymous_token;
showModal('Segnalazione Inviata', `
<div style="padding:16px; background:rgba(16,185,129,0.1); border-radius:var(--border-radius); margin-bottom:16px;">
<p style="font-weight:700; color:var(--success);">Segnalazione ricevuta con codice: <code>${escapeHtml(result.data.report_code)}</code></p>
</div>
${token ? `<p style="font-size:0.875rem; color:var(--gray-600); margin-bottom:8px;">Token per tracking anonimo (conservalo):</p>
<div style="padding:12px; background:var(--gray-100); border-radius:var(--border-radius); font-family:monospace; font-size:0.8rem; word-break:break-all; user-select:all;">${escapeHtml(token)}</div>` : ''}
`, `<button class="btn btn-primary" onclick="closeModal(); switchView('list');">OK</button>`);
e.target.reset();
} else {
showNotification(result.message || 'Errore.', 'error');
}
} catch (err) { showNotification('Errore di connessione.', 'error'); }
finally { btn.disabled = false; btn.textContent = 'Invia Segnalazione'; }
}
async function trackReport() {
const token = document.getElementById('track-token').value.trim();
if (!token) { showNotification('Inserisci il token.', 'error'); return; }
const container = document.getElementById('track-result');
container.innerHTML = '<div class="spinner"></div>';
try {
const result = await api.request('GET', '/whistleblowing/track-anonymous?token=' + encodeURIComponent(token));
if (result.success) {
const r = result.data;
const tl = (r.timeline || []).map(t => `
<div style="display:flex; gap:10px; padding:8px 0; border-bottom:1px solid var(--gray-100);">
<div style="width:6px; height:6px; border-radius:50%; background:var(--primary); margin-top:7px; flex-shrink:0;"></div>
<div><p style="font-size:0.8125rem;">${escapeHtml(t.description)}</p>
<p style="font-size:0.75rem; color:var(--gray-400);">${formatDateTime(t.created_at)}</p></div>
</div>`).join('');
container.innerHTML = `
<div style="padding:16px; border:1px solid var(--gray-200); border-radius:var(--border-radius);">
<div style="display:flex; gap:10px; margin-bottom:12px;">
<code style="font-size:0.875rem;">${escapeHtml(r.report_code)}</code>
<span class="status-badge status-${r.status}">${escapeHtml(statusLabels[r.status] || r.status)}</span>
</div>
<p style="font-size:0.8125rem; color:var(--gray-600); margin-bottom:12px;">Categoria: ${escapeHtml(categoryLabels[r.category] || r.category)}</p>
<div>${tl}</div>
</div>`;
} else {
container.innerHTML = '<div class="empty-state"><h4>Token non valido o segnalazione non trovata</h4></div>';
}
} catch (e) {
container.innerHTML = '<div class="empty-state"><h4>Errore di connessione</h4></div>';
}
}
// Init
loadStats();
loadReports();
</script>
</body>
</html>