Aggiunti endpoint PUT/DELETE /api/assets/subclasses/{id} (org-scoped, le
sottoclassi di sistema restano protette; FK ON DELETE SET NULL scollega i
beni senza cancellarli) + link UI «rinomina»/«rimuovi» accanto a «aggiungi».
Cache-buster api.js + bump version 1.24.8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
565 lines
38 KiB
JavaScript
565 lines
38 KiB
JavaScript
/**
|
|
* 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 }); }
|
|
}
|
|
|
|
// Singleton globale
|
|
const api = new NIS2API();
|