/** * NIS2 Agile - API Client * * Client JavaScript per comunicare con il backend REST API. */ class NIS2API { constructor(baseUrl = '/api') { this.baseUrl = baseUrl; this.token = localStorage.getItem('nis2_access_token'); this.refreshToken = localStorage.getItem('nis2_refresh_token'); this.orgId = localStorage.getItem('nis2_org_id'); } // ═══════════════════════════════════════════════════════════════════ // HTTP Methods // ═══════════════════════════════════════════════════════════════════ async request(method, endpoint, data = null, options = {}) { const url = `${this.baseUrl}${endpoint}`; const headers = { 'Content-Type': 'application/json', }; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } if (this.orgId) { headers['X-Organization-Id'] = this.orgId; } const config = { method, headers }; if (data && (method === 'POST' || method === 'PUT')) { config.body = JSON.stringify(data); } try { const response = await fetch(url, config); const json = await response.json(); // Token expired - try refresh if (response.status === 401 && this.refreshToken && !options._isRetry) { const refreshed = await this.doRefreshToken(); if (refreshed) { return this.request(method, endpoint, data, { ...options, _isRetry: true }); } } if (!json.success && !options.silent) { console.error(`[API] ${method} ${endpoint}:`, json.message); } return json; } catch (error) { console.error(`[API] Network error: ${method} ${endpoint}`, error); const msg = typeof I18n !== 'undefined' ? I18n.t('msg.error_connection') : 'Errore di connessione al server'; return { success: false, message: msg }; } } get(endpoint) { return this.request('GET', endpoint); } post(endpoint, data) { return this.request('POST', endpoint, data); } put(endpoint, data) { return this.request('PUT', endpoint, data); } del(endpoint) { return this.request('DELETE', endpoint); } // ═══════════════════════════════════════════════════════════════════ // Auth // ═══════════════════════════════════════════════════════════════════ async login(email, password) { const result = await this.post('/auth/login', { email, password }); if (result.success) { this.setTokens(result.data.access_token, result.data.refresh_token); this.setUserRole(result.data.user.role); if (result.data.organizations && result.data.organizations.length > 0) { const primary = result.data.organizations.find(o => o.is_primary) || result.data.organizations[0]; this.setOrganization(primary.organization_id); } } return result; } async register(email, password, fullName, roleOrType = 'azienda') { // Supporta sia i nuovi role NIS2 diretti (compliance_manager, org_admin, etc.) // che il vecchio user_type (azienda, consultant) per retrocompatibilità const result = await this.post('/auth/register', { email, password, full_name: fullName, role: roleOrType, user_type: roleOrType, // backward compat }); if (result.success) { this.setTokens(result.data.access_token, result.data.refresh_token); localStorage.setItem('nis2_user_role', result.data.user.role); } return result; } async doRefreshToken() { try { const result = await this.post('/auth/refresh', { refresh_token: this.refreshToken }); if (result.success) { this.setTokens(result.data.access_token, result.data.refresh_token); return true; } } catch (e) { /* ignore */ } this.logout(); return false; } logout() { this.post('/auth/logout', {}).catch(() => {}); this.clearTokens(); window.location.href = '/login.html'; } getMe() { return this.get('/auth/me'); } setTokens(access, refresh) { this.token = access; this.refreshToken = refresh; localStorage.setItem('nis2_access_token', access); localStorage.setItem('nis2_refresh_token', refresh); } clearTokens() { this.token = null; this.refreshToken = null; this.orgId = null; localStorage.removeItem('nis2_access_token'); localStorage.removeItem('nis2_refresh_token'); localStorage.removeItem('nis2_org_id'); localStorage.removeItem('nis2_user_role'); } // Salva ruolo utente al login setUserRole(role) { localStorage.setItem('nis2_user_role', role); } getUserRole() { return localStorage.getItem('nis2_user_role'); } isConsultant() { return this.getUserRole() === 'consultant'; } setOrganization(orgId) { this.orgId = orgId; localStorage.setItem('nis2_org_id', orgId); } isAuthenticated() { return !!this.token; } // ═══════════════════════════════════════════════════════════════════ // Organizations // ═══════════════════════════════════════════════════════════════════ createOrganization(data) { return this.post('/organizations/create', data); } getCurrentOrg() { return this.get('/organizations/current'); } listOrganizations() { return this.get('/organizations/list'); } updateOrganization(id, data) { return this.put(`/organizations/${id}`, data); } classifyEntity(data) { return this.post('/organizations/classify', data); } // ═══════════════════════════════════════════════════════════════════ // Assessments // ═══════════════════════════════════════════════════════════════════ listAssessments() { return this.get('/assessments/list'); } createAssessment(data) { return this.post('/assessments/create', data); } getAssessment(id) { return this.get(`/assessments/${id}`); } getAssessmentQuestions(id) { return this.get(`/assessments/${id}/questions`); } saveAssessmentResponse(id, data) { return this.post(`/assessments/${id}/respond`, data); } completeAssessment(id) { return this.post(`/assessments/${id}/complete`, {}); } getAssessmentReport(id) { return this.get(`/assessments/${id}/report`); } aiAnalyzeAssessment(id) { return this.post(`/assessments/${id}/ai-analyze`, {}); } // ═══════════════════════════════════════════════════════════════════ // Gap Analysis ACN (Determinazione 164179/2025 - misure/requisiti) // I metodi acn* ritornano direttamente il payload `data` e lanciano // un errore {message, error_code} se success=false (per il try/catch UI). // ═══════════════════════════════════════════════════════════════════ async _acn(promise) { const r = await promise; if (!r || !r.success) { const err = new Error((r && r.message) || 'Errore'); err.error_code = r && (r.error_code || (r.data && r.data.error_code)); err.data = r && r.data; throw err; } return r.data; } // ═══════════════════════════════════════════════════════════════════ // Modello Organizzativo SGSI (ISO 27001/27017/27018) — vedi IsmsModelController // Stesso contratto degli acn*: ritornano `data`, lanciano su success=false. // ═══════════════════════════════════════════════════════════════════ ismsGetModel() { return this._acn(this.get('/isms/model')); } ismsSaveModel(data) { return this._acn(this.post('/isms/model', data || {})); } ismsAnnexControls() { return this._acn(this.get('/isms/annex-controls')); } ismsGetSoa() { return this._acn(this.get('/isms/soa')); } ismsDeriveSoa() { return this._acn(this.post('/isms/soa/derive', {})); } ismsUpdateSoa(data) { return this._acn(this.put('/isms/soa', data)); } ismsRoles() { return this._acn(this.get('/isms/roles')); } ismsSaveRole(data) { return this._acn(this.post('/isms/roles', data)); } ismsDeleteRole(id) { return this._acn(this.del(`/isms/roles/${id}`)); } ismsDocuments() { return this._acn(this.get('/isms/documents')); } ismsCreateDocument(data) { return this._acn(this.post('/isms/documents', data)); } ismsAiGenerateDocument(data) { return this._acn(this.post('/isms/documents/ai-generate', data)); } ismsUpdateDocument(id, data) { return this._acn(this.put(`/isms/documents/${id}`, data)); } ismsReadiness() { return this._acn(this.get('/isms/readiness')); } ismsExport() { return this._acn(this.get('/isms/export')); } // ═══════════════════════════════════════════════════════════════════ // Organigramma (A4 Fase 4.1) — ruoli/gerarchia + nodo governance (Art.23). // Stesso contratto degli acn*/isms*: ritornano `data`, lanciano su success=false. // ═══════════════════════════════════════════════════════════════════ orgRolesList() { return this._acn(this.get('/org-roles/list')); } orgRolesAssignableUsers() { return this._acn(this.get('/org-roles/assignable-users')); } orgMembers() { return this._acn(this.get('/organizations/' + (this.orgId || '') + '/members')); } orgRoleGet(id) { return this._acn(this.get(`/org-roles/${id}`)); } orgRoleCreate(data) { return this._acn(this.post('/org-roles/create', data || {})); } orgRoleUpdate(id, data) { return this._acn(this.put(`/org-roles/${id}`, data)); } orgRoleDelete(id) { return this._acn(this.del(`/org-roles/${id}`)); } // ═══════════════════════════════════════════════════════════════════ // Competenze (A4 Fase 4.2) — skill / requisiti ruolo / competenze utente / // mappatura corsi / gap competenze / apertura azione (NCR+CAPA). _acn. // ═══════════════════════════════════════════════════════════════════ compCatalog() { return this._acn(this.get('/competences/catalog')); } compCreateSkill(data) { return this._acn(this.post('/competences/skills', data || {})); } compUpdateSkill(id, data) { return this._acn(this.put(`/competences/skills/${id}`, data)); } compDeleteSkill(id) { return this._acn(this.del(`/competences/skills/${id}`)); } compRoleSkills(roleId) { return this._acn(this.get(`/competences/role-skills/${roleId}`)); } compSetRoleSkill(data) { return this._acn(this.post('/competences/role-skills', data)); } compRemoveRoleSkill(id) { return this._acn(this.del(`/competences/role-skills/${id}`)); } compUserSkills(userId) { return this._acn(this.get(`/competences/user-skills/${userId}`)); } compSetUserSkill(data) { return this._acn(this.post('/competences/user-skills', data)); } compRemoveUserSkill(id) { return this._acn(this.del(`/competences/user-skills/${id}`)); } compMapCourse(data) { return this._acn(this.post('/competences/skill-courses', data)); } compUnmapCourse(id) { return this._acn(this.del(`/competences/skill-courses/${id}`)); } compGapGrid() { return this._acn(this.get('/competences/gap-grid')); } compOpenAction(roleSkillId) { return this._acn(this.post('/competences/open-action', { role_skill_id: roleSkillId })); } // ═══════════════════════════════════════════════════════════════════ // Matrice RACI (A4 Fase 4.3) — hub ruoli↔oggetti (R/A/C/I) + link m2m // (procedura↔inventario, procedura↔rischio, inventario↔rischio, rischio↔misura). // Stesso contratto _acn: ritornano `data`, lanciano su success=false. // NB: i DELETE non hanno body (del() in request() invia body solo per POST/PUT), // quindi i parametri compositi vanno passati come query string → il backend // li legge da getParam() ($_REQUEST). // ═══════════════════════════════════════════════════════════════════ raciMatrix() { return this._acn(this.get('/raci/matrix')); } raciAssign(d) { return this._acn(this.post('/raci/assign', d)); } raciUnassign(d) { return this._acn(this.del('/raci/assign?role_id=' + encodeURIComponent(d.role_id) + '&object_type=' + encodeURIComponent(d.object_type) + '&object_id=' + encodeURIComponent(d.object_id))); } raciObjects(type) { return this._acn(this.get('/raci/objects?type=' + encodeURIComponent(type))); } raciLinks(linkType, id) { return this._acn(this.get('/raci/links?link_type=' + encodeURIComponent(linkType) + '&id=' + encodeURIComponent(id))); } raciLink(d) { return this._acn(this.post('/raci/link', d)); } raciUnlink(d) { return this._acn(this.del('/raci/link?link_type=' + encodeURIComponent(d.link_type) + '&a_id=' + encodeURIComponent(d.a_id) + '&b_id=' + encodeURIComponent(d.b_id))); } // ═══════════════════════════════════════════════════════════════════ // Scadenziario centralizzato (A4 Fase 4.4) — review_schedule. _acn. // Revisioni periodiche (GV.PO-02/GV.SC-07/PR.AT/DE.CM). Status calcolato live. // DELETE per {id} numerico (no body). sync = import idempotente da scadenze esistenti. // Alias rs* === rev* (stessa firma) per compatibilità di naming. // ═══════════════════════════════════════════════════════════════════ revList() { return this._acn(this.get('/review-schedule/list')); } revCreate(d) { return this._acn(this.post('/review-schedule/create', d || {})); } revUpdate(id, d) { return this._acn(this.put(`/review-schedule/${id}`, d)); } revDelete(id) { return this._acn(this.del(`/review-schedule/${id}`)); } revComplete(id) { return this._acn(this.post(`/review-schedule/${id}/complete`, {})); } revSync() { return this._acn(this.post('/review-schedule/sync', {})); } rsList() { return this.revList(); } rsCreate(d) { return this.revCreate(d); } rsUpdate(id, d) { return this.revUpdate(id, d); } rsDelete(id) { return this.revDelete(id); } rsComplete(id) { return this.revComplete(id); } rsSync() { return this.revSync(); } // ═══════════════════════════════════════════════════════════════════ // Framework canonico Misure/Requisiti (Epic C / C1) — SOLA LETTURA. // Catalogo cfg_nis2_* (43 misure / 116 requisiti + procedura+rischio default). // ═══════════════════════════════════════════════════════════════════ frameworkCatalog() { return this._acn(this.get('/framework/catalog')); } frameworkSetState(d) { return this._acn(this.post('/framework/state', d || {})); } // ═══════════════════════════════════════════════════════════════════ // Stakeholder estesi (A4 Fase 4.5) — riusa suppliers (GV.SC-02). // stakeholderMap = vista di sintesi raggruppata (supplier/customer/partner). _acn. // Per aggiungere/etichettare clienti/partner riusare createSupplier/updateSupplier // passando `stakeholder_type` nel data (NB: NON sono _acn → gestire r.success). // ═══════════════════════════════════════════════════════════════════ stakeholderMap() { return this._acn(this.get('/supply-chain/stakeholder-map')); } // ═══════════════════════════════════════════════════════════════════ // Registro Stakeholder + matrice di Mendelow (Epic C / C5). Tutti _acn. // Tipo configurabile (Stak.01-30 + org-added), potere/interesse 0-5, // link organigramma (interni) / procedure (m2m). Quadrante calcolato lato API. // ═══════════════════════════════════════════════════════════════════ stkList() { return this._acn(this.get('/stakeholders/list')); } stkTypes() { return this._acn(this.get('/stakeholders/types')); } stkAddType(d) { return this._acn(this.post('/stakeholders/types', d || {})); } stkQuadrants() { return this._acn(this.get('/stakeholders/quadrants')); } stkPickers() { return this._acn(this.get('/stakeholders/pickers')); } stkCreate(d) { return this._acn(this.post('/stakeholders/create', d || {})); } stkUpdate(id, d) { return this._acn(this.put(`/stakeholders/${id}`, d || {})); } stkDelete(id) { return this._acn(this.del(`/stakeholders/${id}`)); } // ═══════════════════════════════════════════════════════════════════ // Attività stakeholder (Epic C / C5.2). Tutti _acn. // Questionari tipo (template) + attività (questionari/azioni) + assegnazione // (per codice o singoli) + invio (genera magic-link per il portale esterno). // ═══════════════════════════════════════════════════════════════════ stkActTemplates() { return this._acn(this.get('/stakeholder-activities/templates')); } stkActGetTemplate(id) { return this._acn(this.get(`/stakeholder-activities/templates/${id}`)); } stkActCreateTemplate(d) { return this._acn(this.post('/stakeholder-activities/templates', d || {})); } stkActUpdateTemplate(id, d) { return this._acn(this.put(`/stakeholder-activities/templates/${id}`, d || {})); } stkActDeleteTemplate(id) { return this._acn(this.del(`/stakeholder-activities/templates/${id}`)); } stkActPickers() { return this._acn(this.get('/stakeholder-activities/pickers')); } stkActList() { return this._acn(this.get('/stakeholder-activities/list')); } stkActGet(id) { return this._acn(this.get(`/stakeholder-activities/${id}`)); } stkActCreate(d) { return this._acn(this.post('/stakeholder-activities/create', d || {})); } stkActUpdate(id, d) { return this._acn(this.put(`/stakeholder-activities/${id}`, d || {})); } stkActDelete(id) { return this._acn(this.del(`/stakeholder-activities/${id}`)); } stkActAssign(id, d) { return this._acn(this.post(`/stakeholder-activities/${id}/assign`, d || {})); } stkActSend(id) { return this._acn(this.post(`/stakeholder-activities/${id}/send`, {})); } // C5.2b feedback (interno) stkActFeedback(id) { return this._acn(this.get(`/stakeholder-activities/${id}/feedback`)); } stkActComments(id) { return this._acn(this.get(`/stakeholder-activities/${id}/comments`)); } stkActAddComment(id, d) { return this._acn(this.post(`/stakeholder-activities/${id}/comments`, d || {})); } stkActAttachments(id) { return this._acn(this.get(`/stakeholder-activities/${id}/attachments`)); } // ── Audit interni (Modulo A — ISO 27001 §9.2) ── intAuditList() { return this._acn(this.get('/internal-audits/list')); } intAuditGet(id) { return this._acn(this.get(`/internal-audits/${id}`)); } intAuditCreate(data) { return this._acn(this.post('/internal-audits/create', data || {})); } intAuditUpdate(id, data) { return this._acn(this.put(`/internal-audits/${id}`, data)); } intAuditDelete(id) { return this._acn(this.del(`/internal-audits/${id}`)); } intAuditAddItem(data) { return this._acn(this.post('/internal-audits/items', data || {})); } intAuditUpdateItem(itemId, data) { return this._acn(this.put(`/internal-audits/items/${itemId}`, data)); } intAuditRaiseNcr(id, data) { return this._acn(this.post(`/internal-audits/${id}/raise-ncr`, data || {})); } // ── Riesame di Direzione (ISO 27001 §9.3, Modulo B) ── mgmtReviewList() { return this._acn(this.get('/management-reviews/list')); } mgmtReviewGet(id) { return this._acn(this.get(`/management-reviews/${id}`)); } mgmtReviewCreate(data) { return this._acn(this.post('/management-reviews/create', data || {})); } mgmtReviewUpdate(id, data) { return this._acn(this.put(`/management-reviews/${id}`, data || {})); } mgmtReviewGather() { return this._acn(this.get('/management-reviews/gather')); } mgmtReviewAddDecision(id, data) { return this._acn(this.post(`/management-reviews/${id}/decisions`, data || {})); } mgmtReviewUpdateDecision(subId, data) { return this._acn(this.put(`/management-reviews/decisions/${subId}`, data || {})); } mgmtReviewApprove(id) { return this._acn(this.post(`/management-reviews/${id}/approve`, {})); } // ── Calendario unico scadenze (Modulo C) — aggregatore sola lettura ── calendarEvents(params) { const qs = new URLSearchParams(); if (params && params.from) qs.set('from', params.from); if (params && params.to) qs.set('to', params.to); if (params && params.types) qs.set('types', Array.isArray(params.types) ? params.types.join(',') : params.types); const q = qs.toString(); return this._acn(this.get('/calendar/events' + (q ? '?' + q : ''))); } calendarSummary(params) { const qs = new URLSearchParams(); if (params && params.from) qs.set('from', params.from); if (params && params.to) qs.set('to', params.to); const q = qs.toString(); return this._acn(this.get('/calendar/summary' + (q ? '?' + q : ''))); } // ── Controlli periodici (control monitoring §9.1/A.8.16) ── pctlList() { return this._acn(this.get('/periodic-controls/list')); } pctlGet(id) { return this._acn(this.get(`/periodic-controls/${id}`)); } pctlCreate(d) { return this._acn(this.post('/periodic-controls/create', d || {})); } pctlUpdate(id, d) { return this._acn(this.put(`/periodic-controls/${id}`, d || {})); } pctlDelete(id) { return this._acn(this.del(`/periodic-controls/${id}`)); } pctlAddExecution(id, d) { return this._acn(this.post(`/periodic-controls/${id}/executions`, d || {})); } // ═══════════════════════════════════════════════════════════════════ // Dashboard // ═══════════════════════════════════════════════════════════════════ getDashboardOverview() { return this.get('/dashboard/overview'); } getComplianceScore() { return this.get('/dashboard/compliance-score'); } getUpcomingDeadlines() { return this.get('/dashboard/upcoming-deadlines'); } getRecentActivity() { return this.get('/dashboard/recent-activity'); } getRiskHeatmap() { return this.get('/dashboard/risk-heatmap'); } // ═══════════════════════════════════════════════════════════════════ // Risks // ═══════════════════════════════════════════════════════════════════ listRisks(params = {}) { return this.get('/risks/list?' + new URLSearchParams(params)); } createRisk(data) { return this.post('/risks/create', data); } getRisk(id) { return this.get(`/risks/${id}`); } updateRisk(id, data) { return this.put(`/risks/${id}`, data); } deleteRisk(id) { return this.del(`/risks/${id}`); } getRiskMatrix() { return this.get('/risks/matrix'); } aiSuggestRisks() { return this.post('/risks/ai-suggest', {}); } // FAIR quantitativo + KRI (P2) computeFair(id, data) { return this.post(`/risks/${id}/fair`, data); } getFairRegister() { return this.get('/risks/fairRegister'); } listKri() { return this.get('/risks/kri'); } createKri(data) { return this.post('/risks/kri', data); } updateKri(id, data) { return this.put(`/risks/kri/${id}`, data); } // ═══════════════════════════════════════════════════════════════════ // Incidents // ═══════════════════════════════════════════════════════════════════ listIncidents(params = {}) { return this.get('/incidents/list?' + new URLSearchParams(params)); } createIncident(data) { return this.post('/incidents/create', data); } getIncident(id) { return this.get(`/incidents/${id}`); } updateIncident(id, data) { return this.put(`/incidents/${id}`, data); } sendEarlyWarning(id) { return this.post(`/incidents/${id}/early-warning`, {}); } sendNotification(id) { return this.post(`/incidents/${id}/notification`, {}); } sendFinalReport(id) { return this.post(`/incidents/${id}/final-report`, {}); } aiClassifyIncident(id) { return this.post(`/incidents/${id}/aiClassify`, {}); } getIncidentMetrics(id) { return this.get(`/incidents/${id}/metrics`); } getIncidentPir(id) { return this.get(`/incidents/${id}/pir`); } saveIncidentPir(id, data) { return this.post(`/incidents/${id}/pir`, data); } // ═══════════════════════════════════════════════════════════════════ // Policies // ═══════════════════════════════════════════════════════════════════ listPolicies(params = {}) { return this.get('/policies/list?' + new URLSearchParams(params)); } createPolicy(data) { return this.post('/policies/create', data); } getPolicy(id) { return this.get(`/policies/${id}`); } updatePolicy(id, data) { return this.put(`/policies/${id}`, data); } approvePolicy(id) { return this.post(`/policies/${id}/approve`, {}); } aiGeneratePolicy(category) { return this.post('/policies/ai-generate', { category }); } getPolicyTemplates() { return this.get('/policies/templates'); } // ═══════════════════════════════════════════════════════════════════ // Supply Chain // ═══════════════════════════════════════════════════════════════════ listSuppliers() { return this.get('/supply-chain/list'); } createSupplier(data) { return this.post('/supply-chain/create', data); } getSupplier(id) { return this.get(`/supply-chain/${id}`); } updateSupplier(id, data) { return this.put(`/supply-chain/${id}`, data); } assessSupplier(id, data) { return this.post(`/supply-chain/${id}/assess`, data); } // ═══════════════════════════════════════════════════════════════════ // Training // ═══════════════════════════════════════════════════════════════════ listCourses() { return this.get('/training/courses'); } getMyTraining() { return this.get('/training/assignments'); } getTrainingCompliance() { return this.get('/training/compliance-status'); } assignTraining(courseId, userIds, dueDate) { return this.post('/training/assign', { course_id: courseId, user_ids: userIds, due_date: dueDate || null }); } // ═══════════════════════════════════════════════════════════════════ // Assets // ═══════════════════════════════════════════════════════════════════ listAssets(params = {}) { return this.get('/assets/list?' + new URLSearchParams(params)); } createAsset(data) { return this.post('/assets/create', data); } getAsset(id) { return this.get(`/assets/${id}`); } updateAsset(id, data) { return this.put(`/assets/${id}`, data); } deleteAsset(id) { return this.del(`/assets/${id}`); } // NIS2 relevance scoring (GV.OC-04) getScoringGrid() { return this.get('/assets/scoringGrid'); } scoreAsset(id, criteria) { return this.post(`/assets/${id}/score`, { criteria }); } listRelevantSystems() { return this.get('/assets/relevantSystems'); } importAssets(data) { return this.post('/assets/import', data); } // P2 import CMDB/CSV listAssetSubclasses() { return this.get('/assets/subclasses'); } // C4 — voci + sottoclassi addAssetSubclass(data) { return this.post('/assets/subclasses', data); } // C4 — nuova sottoclasse org updateAssetSubclass(id, data) { return this.put('/assets/subclasses/' + id, data); } // C4 — rinomina sottoclasse org (#426) deleteAssetSubclass(id) { return this.del('/assets/subclasses/' + id); } // C4 — rimuove sottoclasse org (#426) getControlsMonitoring() { return this.get('/audit/controlsMonitoring'); } getAcnRequirements() { return this.get('/audit/acnRequirements'); } // requisiti ACN per org updateAcnRequirement(id, status, note) { return this.put(`/audit/acnRequirements/${id}`, { status, evidence_note: note }); } sendSupplierQuestionnaire(id, email) { return this.post(`/supply-chain/${id}/send-questionnaire`, email ? { email } : {}); } // self-assessment fornitore (link esterno) getSupplierQuestionnaireStatus(id) { return this.get(`/supply-chain/${id}/questionnaire-status`); } // P1 continuous control monitoring (JWT) // Modulo questionari configurabile (Fase 1): categorie, template, domande, import getSupplierCategories() { return this.get('/supply-chain/categories'); } createSupplierCategory(data) { return this.post('/supply-chain/categories', data); } updateSupplierCategory(id, data) { return this.put(`/supply-chain/categories/${id}`, data); } deleteSupplierCategory(id) { return this.del(`/supply-chain/categories/${id}`); } getQuestionnaireTemplates() { return this.get('/supply-chain/templates'); } getQuestionnaireTemplate(id) { return this.get(`/supply-chain/templates/${id}`); } createQuestionnaireTemplate(data) { return this.post('/supply-chain/templates', data); } updateQuestionnaireTemplate(id, data) { return this.put(`/supply-chain/templates/${id}`, data); } addTemplateQuestion(templateId, data) { return this.post(`/supply-chain/${templateId}/questions`, data); } updateTemplateQuestion(id, data) { return this.put(`/supply-chain/questions/${id}`, data); } deleteTemplateQuestion(id) { return this.del(`/supply-chain/questions/${id}`); } importSuppliers(suppliers) { return this.post('/supply-chain/import', { suppliers }); } // Campagne questionario (Fase 2) getQuestionnaireCampaigns() { return this.get('/supply-chain/campaigns'); } createQuestionnaireCampaign(supplierId, data) { return this.post(`/supply-chain/${supplierId}/campaigns`, data); } // Campagne questionario (Fase 2) getQuestionnaireCampaigns() { return this.get('/supply-chain/campaigns'); } createQuestionnaireCampaign(supplierId, data) { return this.post(`/supply-chain/${supplierId}/campaigns`, data); } // ═══════════════════════════════════════════════════════════════════ // Audit // ═══════════════════════════════════════════════════════════════════ listControls() { return this.get('/audit/controls'); } updateControl(id, data) { return this.put(`/audit/controls/${id}`, data); } generateComplianceReport() { return this.get('/audit/report'); } getAuditLogs(params = {}) { return this.get('/audit/logs?' + new URLSearchParams(params)); } getIsoMapping() { return this.get('/audit/iso27001-mapping'); } getExecutiveReportUrl() { return this.baseUrl + '/audit/executive-report'; } getExportUrl(type) { return this.baseUrl + '/audit/export?type=' + type; } // ═══════════════════════════════════════════════════════════════════ // Onboarding // ═══════════════════════════════════════════════════════════════════ async uploadVisura(file) { const formData = new FormData(); formData.append('visura', file); const headers = { 'Authorization': 'Bearer ' + this.token }; if (this.orgId) headers['X-Organization-Id'] = this.orgId; try { const response = await fetch(this.baseUrl + '/onboarding/upload-visura', { method: 'POST', headers, body: formData, }); return response.json(); } catch (error) { const msg = typeof I18n !== 'undefined' ? I18n.t('msg.error_connection') : 'Errore di connessione al server'; return { success: false, message: msg }; } } fetchCompany(vatNumber) { return this.post('/onboarding/fetch-company', { vat_number: vatNumber }); } completeOnboarding(data) { return this.post('/onboarding/complete', data); } // ═══════════════════════════════════════════════════════════════════ // Non-Conformity & CAPA // ═══════════════════════════════════════════════════════════════════ listNCRs(params = {}) { return this.get('/ncr/list?' + new URLSearchParams(params)); } createNCR(data) { return this.post('/ncr/create', data); } getNCR(id) { return this.get(`/ncr/${id}`); } updateNCR(id, data) { return this.put(`/ncr/${id}`, data); } addCapa(ncrId, data) { return this.post(`/ncr/${ncrId}/capa`, data); } updateCapa(capaId, data) { return this.put(`/ncr/capa/${capaId}`, data); } createNCRsFromAssessment(assessmentId) { return this.post('/ncr/from-assessment', { assessment_id: assessmentId }); } getNCRStats() { return this.get('/ncr/stats'); } // ═══════════════════════════════════════════════════════════════════ // Feedback & Segnalazioni // ═══════════════════════════════════════════════════════════════════ submitFeedback(data) { return this.post('/feedback/submit', data); } getMyFeedback() { return this.get('/feedback/mine'); } listFeedback(params = {}) { return this.get('/feedback/list?' + new URLSearchParams(params)); } getFeedback(id) { return this.get(`/feedback/${id}`); } updateFeedback(id, data) { return this.put(`/feedback/${id}`, data); } resolveFeedback(id, password) { return this.post(`/feedback/${id}/resolve`, { password }); } // ── Connettori Discovery (mappatura rete/cloud → auto-popola Inventario) ── listDiscoveryConnectors() { return this.get('/discovery-connectors/list'); } createDiscoveryConnector(data) { return this.post('/discovery-connectors/create', data); } getDiscoveryConnector(id) { return this.get(`/discovery-connectors/${id}`); } updateDiscoveryConnector(id, d){ return this.put(`/discovery-connectors/${id}`, d); } deleteDiscoveryConnector(id) { return this.del(`/discovery-connectors/${id}`); } rotateDiscoveryKey(id) { return this.post(`/discovery-connectors/${id}/rotateKey`, {}); } } // Singleton globale const api = new NIS2API();