nis2-agile/public/setup-org.html
Cristiano Benassati ae78a2f7f4 [CORE] Initial project scaffold - NIS2 Agile Compliance Platform
Complete MVP implementation including:
- PHP 8.4 backend with Front Controller pattern (80+ API endpoints)
- Multi-tenant architecture with organization_id isolation
- JWT authentication (HS256, 2h access + 7d refresh tokens)
- 14 controllers: Auth, Organization, Assessment, Dashboard, Risk,
  Incident, Policy, SupplyChain, Training, Asset, Audit, Admin
- AI Service integration (Anthropic Claude API) for gap analysis,
  risk suggestions, policy generation, incident classification
- NIS2 gap analysis questionnaire (~80 questions, 10 categories)
- MySQL schema (20 tables) with NIS2 Art. 21 compliance controls
- NIS2 Art. 23 incident reporting workflow (24h/72h/30d)
- Frontend: login, register, dashboard, assessment wizard, org setup
- Docker configuration (PHP-FPM + Nginx + MySQL)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:50:18 +01:00

378 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Configura Organizzazione - NIS2 Agile</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="app-layout">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar"></aside>
<!-- Main Content -->
<main class="main-content">
<header class="content-header">
<h2>Configurazione Organizzazione</h2>
</header>
<div class="content-body">
<div class="grid-2" style="max-width:960px;">
<!-- Form -->
<div class="card">
<div class="card-header">
<h3>Dati Aziendali</h3>
</div>
<div class="card-body">
<form id="org-form" novalidate>
<div class="form-group">
<label class="form-label" for="company-name">Ragione Sociale <span class="required">*</span></label>
<input type="text" id="company-name" class="form-input"
placeholder="Es. Acme S.r.l." required>
</div>
<div class="form-group">
<label class="form-label" for="vat-number">Partita IVA</label>
<input type="text" id="vat-number" class="form-input"
placeholder="IT12345678901" maxlength="16">
</div>
<div class="form-group">
<label class="form-label" for="sector">Settore <span class="required">*</span></label>
<select id="sector" class="form-select" required>
<option value="">-- Seleziona settore --</option>
<optgroup label="Settori ad Alta Criticita' (Allegato I)">
<option value="energy_electricity">Energia - Elettricita'</option>
<option value="energy_district_heating">Energia - Teleriscaldamento</option>
<option value="energy_oil">Energia - Petrolio</option>
<option value="energy_gas">Energia - Gas</option>
<option value="energy_hydrogen">Energia - Idrogeno</option>
<option value="transport_air">Trasporti - Aereo</option>
<option value="transport_rail">Trasporti - Ferroviario</option>
<option value="transport_water">Trasporti - Marittimo/Fluviale</option>
<option value="transport_road">Trasporti - Stradale</option>
<option value="banking">Banche</option>
<option value="financial_markets">Infrastrutture Mercati Finanziari</option>
<option value="health">Sanita'</option>
<option value="drinking_water">Acqua Potabile</option>
<option value="waste_water">Acque Reflue</option>
<option value="digital_infrastructure">Infrastruttura Digitale</option>
<option value="ict_service_management">Gestione Servizi ICT (B2B)</option>
<option value="public_administration">Pubblica Amministrazione</option>
<option value="space">Spazio</option>
</optgroup>
<optgroup label="Altri Settori Critici (Allegato II)">
<option value="postal_courier">Servizi Postali e Corrieri</option>
<option value="waste_management">Gestione Rifiuti</option>
<option value="chemicals">Fabbricazione Prodotti Chimici</option>
<option value="food">Produzione e Distribuzione Alimentare</option>
<option value="manufacturing_medical">Fabbricazione - Dispositivi Medici</option>
<option value="manufacturing_computers">Fabbricazione - Computer/Elettronica</option>
<option value="manufacturing_electrical">Fabbricazione - Apparecchiature Elettriche</option>
<option value="manufacturing_machinery">Fabbricazione - Macchinari</option>
<option value="manufacturing_vehicles">Fabbricazione - Autoveicoli</option>
<option value="manufacturing_transport">Fabbricazione - Altri Mezzi di Trasporto</option>
<option value="digital_providers">Fornitori Servizi Digitali</option>
<option value="research">Organizzazioni di Ricerca</option>
</optgroup>
<optgroup label="Altro">
<option value="other">Altro Settore</option>
</optgroup>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="employee-count">Numero Dipendenti <span class="required">*</span></label>
<input type="number" id="employee-count" class="form-input"
placeholder="Es. 150" min="1" required>
</div>
<div class="form-group">
<label class="form-label" for="annual-turnover">Fatturato Annuo (EUR) <span class="required">*</span></label>
<input type="number" id="annual-turnover" class="form-input"
placeholder="Es. 15000000" min="0" required>
</div>
</div>
<button type="submit" class="btn btn-primary btn-lg w-full mt-16" id="save-btn">
Salva e Continua
</button>
</form>
</div>
</div>
<!-- Classification Preview -->
<div>
<div class="card">
<div class="card-header">
<h3>Classificazione NIS2</h3>
</div>
<div class="card-body">
<div class="classification-preview not-applicable" id="classification-preview">
<div class="classification-label">In Attesa</div>
<p class="classification-desc">
Compila i dati aziendali per ottenere la classificazione automatica
secondo i criteri della Direttiva NIS2 (UE) 2022/2555.
</p>
</div>
<div class="mt-24" id="classification-details" style="display:none;">
<h4 style="font-size:0.875rem; margin-bottom:12px; color:var(--gray-700);">Dettagli Classificazione</h4>
<div id="classification-info" style="font-size:0.8125rem; color:var(--gray-600); line-height:1.7;"></div>
</div>
</div>
</div>
<div class="card mt-24">
<div class="card-header">
<h3>Criteri di Classificazione</h3>
</div>
<div class="card-body" style="font-size:0.8125rem; color:var(--gray-600); line-height:1.7;">
<p><strong>Soggetti Essenziali:</strong></p>
<ul style="padding-left:20px; margin-bottom:12px;">
<li>Settori Allegato I con &ge; 250 dipendenti o fatturato &ge; 50M EUR</li>
<li>Alcuni soggetti designati indipendentemente dalle dimensioni</li>
</ul>
<p><strong>Soggetti Importanti:</strong></p>
<ul style="padding-left:20px; margin-bottom:12px;">
<li>Settori Allegato I/II con &ge; 50 dipendenti o fatturato &ge; 10M EUR</li>
<li>Non qualificati come essenziali</li>
</ul>
<p><strong>Non Applicabile:</strong></p>
<ul style="padding-left:20px;">
<li>Organizzazioni sotto le soglie minime</li>
<li>Settori non coperti dalla direttiva</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="js/api.js"></script>
<script src="js/common.js"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
// ── Auto-classify on input change ────────────────────────
const sectorSelect = document.getElementById('sector');
const employeeInput = document.getElementById('employee-count');
const turnoverInput = document.getElementById('annual-turnover');
const debouncedClassify = debounce(autoClassify, 500);
sectorSelect.addEventListener('change', debouncedClassify);
employeeInput.addEventListener('input', debouncedClassify);
turnoverInput.addEventListener('input', debouncedClassify);
async function autoClassify() {
const sector = sectorSelect.value;
const employees = parseInt(employeeInput.value) || 0;
const turnover = parseInt(turnoverInput.value) || 0;
if (!sector || employees <= 0) {
resetClassification();
return;
}
try {
const result = await api.classifyEntity({
sector: sector,
employee_count: employees,
annual_turnover: turnover
});
if (result.success && result.data) {
updateClassificationUI(result.data);
} else {
// Fallback: classificazione locale
const localResult = classifyLocally(sector, employees, turnover);
updateClassificationUI(localResult);
}
} catch (e) {
// Fallback: classificazione locale
const localResult = classifyLocally(sector, employees, turnover);
updateClassificationUI(localResult);
}
}
function classifyLocally(sector, employees, turnover) {
// Settori Allegato I (alta criticita')
const annexI = [
'energy_electricity', 'energy_district_heating', 'energy_oil', 'energy_gas',
'energy_hydrogen', 'transport_air', 'transport_rail', 'transport_water',
'transport_road', 'banking', 'financial_markets', 'health', 'drinking_water',
'waste_water', 'digital_infrastructure', 'ict_service_management',
'public_administration', 'space'
];
// Settori Allegato II
const annexII = [
'postal_courier', 'waste_management', 'chemicals', 'food',
'manufacturing_medical', 'manufacturing_computers', 'manufacturing_electrical',
'manufacturing_machinery', 'manufacturing_vehicles', 'manufacturing_transport',
'digital_providers', 'research'
];
const isAnnexI = annexI.includes(sector);
const isAnnexII = annexII.includes(sector);
const isLarge = employees >= 250 || turnover >= 50000000;
const isMedium = employees >= 50 || turnover >= 10000000;
if (isAnnexI && isLarge) {
return {
classification: 'essential',
label: 'Soggetto Essenziale',
description: 'La vostra organizzazione rientra tra i soggetti essenziali ai sensi della Direttiva NIS2, in quanto opera in un settore ad alta criticita\' (Allegato I) e supera le soglie dimensionali per la classificazione come grande impresa.'
};
} else if ((isAnnexI || isAnnexII) && isMedium) {
return {
classification: 'important',
label: 'Soggetto Importante',
description: 'La vostra organizzazione rientra tra i soggetti importanti ai sensi della Direttiva NIS2. Siete tenuti a rispettare gli obblighi di sicurezza e di notifica degli incidenti, con un regime di vigilanza ex post.'
};
} else if (sector === 'other' || (!isAnnexI && !isAnnexII)) {
return {
classification: 'not_applicable',
label: 'Non Applicabile',
description: 'In base ai dati forniti, la vostra organizzazione non sembra rientrare nell\'ambito di applicazione della Direttiva NIS2. Consigliamo comunque di verificare con le autorita\' competenti.'
};
} else {
return {
classification: 'not_applicable',
label: 'Sotto le Soglie',
description: 'La vostra organizzazione opera in un settore coperto dalla NIS2 ma non raggiunge le soglie dimensionali minime (50 dipendenti o 10M EUR di fatturato). Potreste comunque essere designati dalle autorita\' nazionali.'
};
}
}
function updateClassificationUI(data) {
const preview = document.getElementById('classification-preview');
const details = document.getElementById('classification-details');
const info = document.getElementById('classification-info');
const classification = data.classification || data.type || 'not_applicable';
const label = data.label || classification;
const description = data.description || data.explanation || '';
// Reset classes
preview.className = 'classification-preview';
if (classification === 'essential' || classification === 'essenziale') {
preview.classList.add('essential');
preview.innerHTML = `
<div class="classification-label">Soggetto Essenziale</div>
<p class="classification-desc">${escapeHtml(description) || 'Obblighi completi NIS2 - Vigilanza ex ante.'}</p>
`;
} else if (classification === 'important' || classification === 'importante') {
preview.classList.add('important');
preview.innerHTML = `
<div class="classification-label">Soggetto Importante</div>
<p class="classification-desc">${escapeHtml(description) || 'Obblighi NIS2 - Vigilanza ex post.'}</p>
`;
} else {
preview.classList.add('not-applicable');
preview.innerHTML = `
<div class="classification-label">${escapeHtml(label)}</div>
<p class="classification-desc">${escapeHtml(description) || 'Non rientra nell\'ambito NIS2.'}</p>
`;
}
if (data.details || data.obligations) {
details.style.display = 'block';
let detailsHtml = '';
if (data.obligations && data.obligations.length > 0) {
detailsHtml += '<p><strong>Obblighi principali:</strong></p><ul style="padding-left:20px;">';
data.obligations.forEach(o => {
detailsHtml += `<li>${escapeHtml(o)}</li>`;
});
detailsHtml += '</ul>';
}
if (data.details) {
detailsHtml += `<p>${escapeHtml(data.details)}</p>`;
}
info.innerHTML = detailsHtml;
}
}
function resetClassification() {
const preview = document.getElementById('classification-preview');
preview.className = 'classification-preview not-applicable';
preview.innerHTML = `
<div class="classification-label">In Attesa</div>
<p class="classification-desc">
Compila i dati aziendali per ottenere la classificazione automatica
secondo i criteri della Direttiva NIS2 (UE) 2022/2555.
</p>
`;
document.getElementById('classification-details').style.display = 'none';
}
// ── Form Submit ──────────────────────────────────────────
document.getElementById('org-form').addEventListener('submit', async (e) => {
e.preventDefault();
const companyName = document.getElementById('company-name').value.trim();
const vatNumber = document.getElementById('vat-number').value.trim();
const sector = sectorSelect.value;
const employees = parseInt(employeeInput.value) || 0;
const turnover = parseInt(turnoverInput.value) || 0;
// Validazione
if (!companyName) {
showNotification('Inserisci la ragione sociale.', 'warning');
return;
}
if (!sector) {
showNotification('Seleziona un settore.', 'warning');
return;
}
if (employees <= 0) {
showNotification('Inserisci il numero di dipendenti.', 'warning');
return;
}
if (turnover <= 0) {
showNotification('Inserisci il fatturato annuo.', 'warning');
return;
}
const saveBtn = document.getElementById('save-btn');
saveBtn.disabled = true;
saveBtn.textContent = 'Salvataggio in corso...';
try {
const result = await api.createOrganization({
name: companyName,
vat_number: vatNumber,
sector: sector,
employee_count: employees,
annual_turnover: turnover
});
if (result.success && result.data) {
// Imposta l'organizzazione come attiva
api.setOrganization(result.data.id || result.data.organization_id);
showNotification('Organizzazione creata con successo!', 'success');
setTimeout(() => {
window.location.href = 'dashboard.html';
}, 1000);
} else {
showNotification(result.message || 'Errore nella creazione.', 'error');
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
} finally {
saveBtn.disabled = false;
saveBtn.textContent = 'Salva e Continua';
}
});
</script>
</body>
</html>