/
home
/
techb158
/
balavpn.abdallabala.com
/
/home/techb158/balavpn.abdallabala.com
mkdir
upload
Name
Size
Mode
Actions
.git/
-
0755
rm
.github/
-
0755
rm
.next/
-
0777
rm
.well-known/
-
0755
rm
blxsenk/
-
0755
rm
bsahcio/
-
0755
rm
cgi-bin/
-
0755
rm
deploy/
-
0777
rm
docs/
-
0777
rm
emnbsuk/
-
0755
rm
gqefxrw/
-
0755
rm
jmvybrk/
-
0755
rm
ne1uwzb/
-
0755
rm
node_modules/
-
0777
rm
ojyauew/
-
0755
rm
oyqfklp/
-
0755
rm
prisma/
-
0777
rm
public/
-
0777
rm
rxfwzbq/
-
0755
rm
saas-app/
-
0755
rm
scripts/
-
0777
rm
src/
-
0755
rm
tests/
-
0777
rm
trwaeuc/
-
0755
rm
xqcyzpj/
-
0755
rm
yirxnfc/
-
0755
rm
.dockerignore
164
0644
edit
dl
rm
.env
1808
0666
edit
dl
rm
.env-backup
249
0666
edit
dl
rm
.env.example
1962
0644
edit
dl
rm
.env.production
1473
0644
edit
dl
rm
.gitignore
74
0644
edit
dl
rm
.htaccess
281
0644
edit
dl
rm
deploy.log
7656
0644
edit
dl
rm
docker-compose.monitoring.yml
254
0644
edit
dl
rm
docker-compose.prod.yml
3400
0644
edit
dl
rm
docker-compose.supabase.yml
1043
0644
edit
dl
rm
docker-compose.yml
2295
0644
edit
dl
rm
Dockerfile
1310
0644
edit
dl
rm
jsconfig.json
95
0644
edit
dl
rm
local-server.err.log
0
0666
edit
dl
rm
local-server.js
49177
0644
edit
dl
rm
local-server.out.log
0
0666
edit
dl
rm
mock-oidc-provider.js
7016
0644
edit
dl
rm
next.config.mjs
219
0644
edit
dl
rm
package-lock.json
237656
0644
edit
dl
rm
package.json
2296
0644
edit
dl
rm
README.md
2150
0644
edit
dl
rm
saas-app.zip
223122322
0644
edit
dl
rm
truncate_demo.sql
1676
0644
edit
dl
rm
wp-cron-owhg.php
1659
0644
edit
dl
rm
Edit:
/home/techb158/balavpn.abdallabala.com/local-server.js
(49177B)
const http = require("http"); const fs = require("fs"); const path = require("path"); const { calculateDashboard, scoreRisk } = require("./src/domain/risk-engine"); const { ROLE_PERMISSIONS } = require("./src/domain/permissions"); const { listSupportedProviders, riskStatusToExternalStatus } = require("./src/domain/integrations"); const PORT = Number(process.env.PORT || 8090); const source = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "data", "database.json"), "utf8")); const tables = source.tables; function makeId(prefix) { return `${prefix}-${Date.now().toString(36).toUpperCase()}-${Math.random().toString(36).slice(2, 8).toUpperCase()}`; } function now() { return new Date().toISOString(); } function slug(value) { return String(value || "item").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); } function sendJson(res, status, payload) { res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "Content-Type, X-Cosmic-User-Email", "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS" }); res.end(JSON.stringify(payload, null, 2)); } function sendText(res, status, body, type = "text/plain; charset=utf-8") { res.writeHead(status, { "Content-Type": type, "Cache-Control": "no-store", "Access-Control-Allow-Origin": "*" }); res.end(body); } function parseBody(req) { return new Promise((resolve, reject) => { let body = ""; req.on("data", c => { body += c; }); req.on("end", () => { if (!body) return resolve({}); try { resolve(JSON.parse(body)); } catch (e) { reject(e); } }); }); } function riskStatus(v) { if (v === "In mitigation") return "IN_MITIGATION"; if (v === "Accepted") return "ACCEPTED"; if (v === "Closed") return "CLOSED"; return v || "OPEN"; } function approvalStatus(v) { if (v === "Approved") return "APPROVED"; if (v === "Rejected") return "REJECTED"; if (v === "Accepted") return "ACCEPTED"; return v || "PENDING"; } function mitigationStatus(v) { if (v === "In progress") return "IN_PROGRESS"; if (v === "Done") return "DONE"; if (v === "Rejected") return "REJECTED"; return v || "NOT_STARTED"; } function publicUser(user) { return { id: user.id, email: user.email, displayName: user.displayName, role: user.role, permissions: user.permissions, status: user.status || "ACTIVE" }; } const sourceProject = tables.projects[0]; const state = { organizations: [{ id: "org-demo", name: "COSMIC Demo Organization", slug: "cosmic-demo", status: "TRIAL", planCode: "pilot", manualBilling: true, allowedDomains: ["cosmic.local"], dataRetentionDays: 365 }], workspaces: [{ id: "workspace-demo", organizationId: "org-demo", name: "AI Risk Governance", slug: "ai-risk-governance", type: "B2B_TEAM", status: "TRIAL" }], users: [ { id: "user-owner", organizationId: "org-demo", email: process.env.COSMIC_BOOTSTRAP_EMAIL || "super-owner@cosmic.local", displayName: process.env.COSMIC_BOOTSTRAP_NAME || "COSMIC Platform Owner", role: "owner", permissions: ROLE_PERMISSIONS.owner, status: "ACTIVE" }, { id: "user-viewer", organizationId: "org-demo", email: "viewer@cosmic.local", displayName: "Read-only Viewer", role: "viewer", permissions: ROLE_PERMISSIONS.viewer, status: "ACTIVE" } ], subscriptions: [{ id: "sub-demo", organizationId: "org-demo", source: "MANUAL", status: "TRIALING", planCode: "pilot", adminOverride: true, limits: { users: 10, projects: 5, reportsPerMonth: 100, integrations: 4, storageMb: 1024 } }], projects: [], gates: [], auditEvents: [], invitations: [], integrations: [], mappings: [], syncRuns: [], reportExports: [] }; const project = { id: "project-demo", workspaceId: "workspace-demo", name: sourceProject.name, subtitle: sourceProject.subtitle, projectType: sourceProject.project_type, currentLifecyclePhase: sourceProject.current_lifecycle_phase, riskAppetite: sourceProject.risk_appetite, assessmentDate: sourceProject.assessment_date, status: sourceProject.status, ownerDisplayName: sourceProject.owner_display_name, description: sourceProject.description, thresholds: sourceProject.thresholds || source.thresholds || {}, reviews: source.project_reviews[sourceProject.id] || {}, lifecyclePhases: [], risks: [], indicators: [], experiments: [] }; project.lifecyclePhases = tables.lifecycle_phases.filter(p => p.project_id === sourceProject.id).map(p => ({ id: p.id, projectId: project.id, name: p.name, status: p.status, readinessScore: p.readiness_score, sequence: p.sequence })); project.indicators = tables.indicators.map(i => ({ id: i.id, projectId: project.id, name: i.name, dimension: i.dimension, measurand: i.measurand, unit: i.unit, target: i.target, interpretationRule: i.interpretation_rule })); project.risks = tables.risks.filter(r => r.project_id === sourceProject.id).map(r => ({ id: r.id, projectId: project.id, ownerDisplayName: r.owner_display_name, title: r.title, description: r.description || "", dimension: r.dimension, domain: r.domain, lifecyclePhase: r.lifecycle_phase, probability: r.probability, impact: r.impact, detectability: r.detectability, status: riskStatus(r.status), approvalStatus: approvalStatus(r.approval_status), dueDate: r.due_date, evidenceSummary: r.evidence_summary, mitigations: [], evidence: [] })); tables.mitigation_actions.forEach(m => { const r = project.risks.find(x => x.id === m.risk_id); if (r) r.mitigations.push({ id: m.id, riskId: r.id, ownerDisplayName: m.owner_display_name, title: m.title, description: m.description || "", status: mitigationStatus(m.status), progressPercent: Number(m.progress_percent || 0), effectivenessPercent: Number(m.effectiveness_percent || 0), dueDate: m.due_date, sequence: m.sequence || 1, evidence: [] }); }); tables.evidence_artifacts.forEach(e => { const r = project.risks.find(x => x.id === e.risk_id); if (r) r.evidence.push({ id: e.id, projectId: project.id, riskId: r.id, mitigationId: e.mitigation_id, type: e.type, title: e.title, uri: e.uri }); }); project.experiments = tables.experiments.map(e => ({ id: e.id, projectId: project.id, name: e.name, modelName: e.model_name, selected: e.selected === true, metrics: tables.model_metrics.filter(m => m.experiment_id === e.id).map(m => ({ id: m.id, experimentId: e.id, metricName: m.metric_name, metricValue: m.metric_value, thresholdValue: m.threshold_value, status: m.status, measuredAt: m.measured_at })) })); state.projects.push(project); state.integrations = tables.project_management_integrations.map(i => ({ id: i.id, workspaceId: "workspace-demo", provider: i.provider === "Microsoft Planner" ? "MICROSOFT_PLANNER" : i.provider.toUpperCase(), workspaceName: i.workspace_name, externalProjectKey: i.external_project_key, baseUrl: i.base_url, authMode: i.auth_mode, connectionStatus: "NEEDS_CONFIGURATION", syncDirection: i.sync_direction, liveEnabled: false, liveConfig: i.live_config_json ? JSON.parse(i.live_config_json) : {} })); function audit(action, entityType, entityId, projectId = null, afterJson = null) { const evt = { id: makeId("audit"), organizationId: "org-demo", workspaceId: "workspace-demo", projectId, actorUserId: "user-owner", entityType, entityId, action, afterJson, createdAt: now() }; state.auditEvents.unshift(evt); return evt; } function getProject(id) { return state.projects.find(p => p.id === id); } function getRisk(id) { for (const p of state.projects) { const r = p.risks.find(x => x.id === id); if (r) return { project: p, risk: r }; } return {}; } function getMitigation(id) { for (const p of state.projects) for (const r of p.risks) { const m = r.mitigations.find(x => x.id === id); if (m) return { project: p, risk: r, mitigation: m }; } return {}; } function getDashboard(projectId) { const p = getProject(projectId); return p ? calculateDashboard(p) : null; } function getAllMitigations(projectId) { const p = getProject(projectId); if (!p) return []; return p.risks.flatMap(r => r.mitigations.map(m => ({ ...m, riskId: r.id, riskTitle: r.title, riskStatus: r.status }))); } function fullState() { return { organizations: state.organizations, workspaces: state.workspaces, users: state.users.map(publicUser), subscriptions: state.subscriptions, projects: state.projects.map(p => ({ id: p.id, workspaceId: p.workspaceId, name: p.name, status: p.status })), integrations: state.integrations, auditEvents: state.auditEvents.slice(0, 50) }; } function csv(rows, headers) { return [headers.join(",")].concat(rows.map(row => headers.map(h => { const v = row[h] == null ? "" : String(row[h]); return /[",\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v; }).join(","))).join("\n"); } function displayRiskStatus(value) { const v = String(value || "OPEN").toUpperCase(); if (v === "IN_MITIGATION") return "In mitigation"; if (v === "ACCEPTED") return "Accepted"; if (v === "CLOSED") return "Closed"; return "Open"; } function displayApprovalStatus(value) { const v = String(value || "PENDING").toUpperCase(); if (v === "APPROVED") return "Approved"; if (v === "REJECTED") return "Rejected"; if (v === "ACCEPTED") return "Accepted"; return "Pending"; } function displayMitigationStatus(value) { const v = String(value || "NOT_STARTED").toUpperCase(); if (v === "IN_PROGRESS") return "In progress"; if (v === "DONE") return "Done"; if (v === "REJECTED") return "Rejected"; return "Not started"; } function providerLabel(value) { return String(value || "").replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()).replace("Microsoft Planner", "Microsoft Planner"); } function legacyRisk(scoredRisk) { const mitigationActions = (scoredRisk.mitigations || []).map(m => ({ ...m, status: displayMitigationStatus(m.status), progress_percent: m.progressPercent, effectiveness_percent: m.effectivenessPercent, owner: m.ownerDisplayName || "Unassigned", evidenceArtifacts: m.evidence || [] })); return { ...scoredRisk, status: displayRiskStatus(scoredRisk.status), approvalStatus: displayApprovalStatus(scoredRisk.approvalStatus), owner: scoredRisk.ownerDisplayName || scoredRisk.owner || "Unassigned", evidence: scoredRisk.evidenceSummary || scoredRisk.description || "", mitigationActions, mitigations: mitigationActions.map(m => m.title) }; } function legacyMitigation(item) { return { ...item, status: displayMitigationStatus(item.status), progress_percent: item.progressPercent, effectiveness_percent: item.effectivenessPercent, owner: item.ownerDisplayName || "Unassigned", evidenceArtifacts: item.evidence || [] }; } function legacyCriterion(item, index, gateId = null) { return { id: item.id || (gateId ? `${gateId}-criterion-${index + 1}` : `criterion-${index + 1}`), gateId, name: item.name, expectedRule: item.required, required: item.required, actualValue: item.actual, actual: item.actual, status: item.status, evidence: item.evidence, reviewerStatus: item.reviewerStatus || "Open", reviewerNote: item.reviewerNote || "", evidenceUri: item.evidenceUri || "", evidenceTitle: item.evidenceTitle || item.evidence }; } function legacyGate(gate) { const criteria = (gate.criteria || []).map((item, index) => legacyCriterion(item, index, gate.id)); const latestDecision = gate.decisions && gate.decisions.length ? gate.decisions[gate.decisions.length - 1] : null; return { ...gate, criteria, reviewStatus: latestDecision ? latestDecision.decision : "Pending review", latestDecision, blockingCriteria: criteria.filter(c => c.status === "Blocked").length, reviewerNotes: criteria.filter(c => c.reviewerNote).length, evidenceLinks: criteria.filter(c => c.evidenceUri).length, evaluatedAt: gate.evaluatedAt || gate.createdAt }; } function legacyDashboardPayload() { const dashboard = getDashboard("project-demo"); const p = getProject("project-demo"); const scoredRisks = dashboard.scoredRisks.map(legacyRisk); const legacyDashboard = { ...dashboard, project: { ...dashboard.project, id: p.id, name: p.name, subtitle: p.subtitle, projectType: p.projectType, lifecyclePhase: p.currentLifecyclePhase, assessmentDate: p.assessmentDate, owner: p.ownerDisplayName, riskAppetite: p.riskAppetite, description: p.description }, lifecycle: dashboard.lifecycle.map(phase => ({ ...phase, readiness: phase.readinessScore || phase.readiness || 0, status: phase.status || phase.riskLevel })), gate: { ...dashboard.gate, criteria: dashboard.gate.criteria.map((item, index) => legacyCriterion(item, index)) }, scoredRisks, topRisks: scoredRisks.slice().filter(r => r.status !== "Closed").sort((a, b) => b.residualScore - a.residualScore).slice(0, 8), mitigations: dashboard.mitigations.map(legacyMitigation), indicators: (dashboard.indicators || []).map(i => ({ ...i, interpretation: i.interpretationRule || i.interpretation })), experiments: (dashboard.experiments || []).map(e => ({ ...e, model: e.modelName, accuracy: (e.metrics.find(m => m.metricName === "accuracy") || {}).metricValue, f1: (e.metrics.find(m => m.metricName === "f1") || {}).metricValue, precision: (e.metrics.find(m => m.metricName === "precision") || {}).metricValue, recall: (e.metrics.find(m => m.metricName === "recall") || {}).metricValue, auc: (e.metrics.find(m => m.metricName === "auc") || {}).metricValue, stability: (e.metrics.find(m => m.metricName === "stability") || {}).metricValue, reproducibility: "Reviewed", biasStatus: "Pending" })) }; return { data: { project: legacyDashboard.project }, dashboard: legacyDashboard }; } function legacyAccessPayload() { const permissions = Array.from(new Set(Object.values(ROLE_PERMISSIONS).flat())); return { actor: { ...publicUser(state.users[0]), roleName: "Owner", active: true }, users: state.users.map(u => ({ ...publicUser(u), roleName: u.role.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()), active: true })), roles: Object.entries(ROLE_PERMISSIONS).map(([code, perms]) => ({ id: code, name: code.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()), permissions: perms })), permissions }; } function legacyIntegrations(workspaceId = "workspace-demo") { return state.integrations.filter(i => i.workspaceId === workspaceId).map(i => ({ ...i, provider: providerLabel(i.provider), connectionStatus: i.connectionStatus === "CONNECTED" ? "Connected" : "Needs configuration", capabilities: { workItemName: "Work item", riskFieldName: "COSMIC risk score", mitigationObjectName: "Mitigation action" } })); } function sendFile(res, filePath, type) { fs.readFile(filePath, (error, content) => { if (error) return sendText(res, 404, "Not found"); sendText(res, 200, content, type); }); } function staticType(pathname) { if (pathname.endsWith(".css")) return "text/css; charset=utf-8"; if (pathname.endsWith(".js")) return "application/javascript; charset=utf-8"; if (pathname.endsWith(".json")) return "application/json; charset=utf-8"; if (pathname.endsWith(".html")) return "text/html; charset=utf-8"; return "application/octet-stream"; } function appHtml() { return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>COSMIC AI-Risk SaaS Local</title><style> :root{--bg:#f6faf8;--panel:#fff;--ink:#10201b;--muted:#60706b;--line:#d9e5df;--accent:#0d765c;--danger:#b42318}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font-family:Arial,Helvetica,sans-serif}header{background:#fff;border-bottom:1px solid var(--line);padding:16px 22px;display:flex;justify-content:space-between;gap:16px;align-items:center;position:sticky;top:0;z-index:2}.brand{font-weight:800;color:var(--accent)}nav{display:flex;gap:8px;flex-wrap:wrap}button,.btn,input,select,textarea{font:inherit}button,.btn{border:1px solid var(--accent);background:var(--accent);color:#fff;border-radius:6px;padding:8px 11px;font-weight:700;cursor:pointer;text-decoration:none}button.secondary,.btn.secondary{background:#fff;color:var(--accent)}button.danger{border-color:var(--danger);background:var(--danger)}main{width:min(1240px,100%);margin:0 auto;padding:20px}.tabs{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}.tab{background:#fff;color:var(--ink);border-color:var(--line)}.tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}.view{display:none}.view.active{display:block}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px}.two{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,.55fr);gap:14px}.panel{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:16px;margin-bottom:14px}.metric{font-size:34px;font-weight:800;color:var(--accent)}.muted{color:var(--muted)}table{width:100%;border-collapse:collapse;background:#fff}th,td{padding:9px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}th{font-size:12px;text-transform:uppercase;color:var(--muted)}input,select,textarea{width:100%;border:1px solid var(--line);border-radius:6px;padding:8px;background:#fff}.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.badge{display:inline-flex;padding:3px 8px;border-radius:999px;background:#e6f4ef;color:#075f49;font-weight:700;font-size:12px}.toast{position:fixed;right:16px;bottom:16px;background:#10201b;color:#fff;padding:12px 14px;border-radius:8px;display:none}.scroll{overflow:auto;max-height:520px}@media(max-width:860px){.two{grid-template-columns:1fr}header{align-items:flex-start;flex-direction:column}} </style></head><body><header><div><div class="brand">COSMIC AI-Risk SaaS Local</div><div class="muted" id="tenantLine">Loading tenant</div></div><nav><a class="btn secondary" href="/api/health">Health</a><a class="btn secondary" href="/api/ready">Ready</a><button data-action="refresh">Refresh</button></nav></header><main><div class="tabs" id="tabs"></div><section id="overview" class="view active"></section><section id="risks" class="view"></section><section id="mitigations" class="view"></section><section id="gate" class="view"></section><section id="reports" class="view"></section><section id="integrations" class="view"></section><section id="admin" class="view"></section></main><div class="toast" id="toast"></div><script> const tabs=[['overview','Overview'],['risks','Risks'],['mitigations','Mitigations'],['gate','Gate'],['reports','Reports'],['integrations','Integrations'],['admin','Admin']]; let app={projectId:'project-demo',workspaceId:'workspace-demo',dashboard:null,state:null,risks:[]}; function api(path,options={}){return fetch(path,{method:options.method||'GET',headers:{'Content-Type':'application/json'},body:options.body?JSON.stringify(options.body):undefined}).then(async r=>{const t=r.headers.get('content-type')||'';const b=t.includes('json')?await r.json():await r.text();if(!r.ok)throw new Error(b.error||r.statusText);return b})} function toast(msg){const el=document.getElementById('toast');el.textContent=msg;el.style.display='block';setTimeout(()=>el.style.display='none',2600)} function esc(v){return String(v??'').replace(/[&<>]/g,s=>({'&':'&','<':'<','>':'>'}[s]))} function setTab(id){document.querySelectorAll('.view').forEach(v=>v.classList.remove('active'));document.querySelectorAll('.tab').forEach(v=>v.classList.remove('active'));document.getElementById(id).classList.add('active');document.querySelector('[data-target="'+id+'"]').classList.add('active')} function initTabs(){document.getElementById('tabs').innerHTML=tabs.map(([id,label])=>'<button class="tab '+(id==='overview'?'active':' ')+'" data-action="tab" data-target="'+id+'">'+label+'</button>').join('')} async function refreshAll(){app.state=await api('/api/local/state');app.dashboard=(await api('/api/projects/'+app.projectId+'/dashboard')).dashboard;app.risks=(await api('/api/projects/'+app.projectId+'/risks')).risks;document.getElementById('tenantLine').textContent=app.state.organizations[0].name+' / '+app.state.workspaces[0].name;renderOverview();renderRisks();renderMitigations();renderGate();renderReports();renderIntegrations();renderAdmin()} function cards(){const s=app.dashboard.summary;return '<div class="grid"><div class="panel"><h3>Overall Score</h3><div class="metric">'+s.overallScore+'</div><span class="badge">'+s.riskLevel+'</span></div><div class="panel"><h3>Gate</h3><div class="metric">'+s.gateStatus+'</div><p class="muted">'+esc(app.dashboard.gate.message)+'</p></div><div class="panel"><h3>Open Risks</h3><div class="metric">'+s.openRisks+'</div><p class="muted">'+s.totalRisks+' total risks</p></div><div class="panel"><h3>Mitigation</h3><div class="metric">'+s.mitigationCompletion+'%</div><p class="muted">Average active progress</p></div></div>'} function riskTable(rows,actions=true){return '<div class="scroll"><table><thead><tr><th>Risk</th><th>Dimension</th><th>Status</th><th>Residual</th>'+(actions?'<th>Actions</th>':'')+'</tr></thead><tbody>'+rows.map(r=>'<tr><td>'+esc(r.title)+'</td><td>'+r.dimension+'</td><td>'+r.status+'</td><td>'+r.residualScore+'</td>'+(actions?'<td><button data-action="close-risk" data-id="'+r.id+'">Close</button> <button class="secondary" data-action="delete-risk" data-id="'+r.id+'">Delete</button></td>':'')+'</tr>').join('')+'</tbody></table></div>'} function renderOverview(){document.getElementById('overview').innerHTML=cards()+'<div class="two"><div class="panel"><h2>Top risks</h2>'+riskTable(app.dashboard.topRisks.slice(0,8),false)+'</div><div class="panel"><h2>Governance dimensions</h2>'+Object.entries(app.dashboard.dimensions).map(([k,v])=>'<p><b>'+k+'</b>: '+v+'</p>').join('')+'<h2>Lifecycle</h2>'+app.dashboard.lifecycle.map(x=>'<p><b>'+x.name+'</b>: '+x.riskScore+' / '+x.riskLevel+'</p>').join('')+'</div></div>'} function renderRisks(){document.getElementById('risks').innerHTML='<div class="two"><div class="panel"><h2>Risk register</h2>'+riskTable(app.risks,true)+'</div><div class="panel"><h2>Add risk</h2><form onsubmit="createRisk(event)" class="form-grid"><input name="title" placeholder="Risk title" required><select name="dimension"><option>Technical</option><option>Organizational</option><option>Human</option></select><input name="probability" type="number" min="1" max="5" value="3"><input name="impact" type="number" min="1" max="5" value="4"><input name="detectability" type="number" min="1" max="5" value="3"><textarea name="description" placeholder="Description"></textarea><button>Add risk</button></form></div></div>'} async function createRisk(e){e.preventDefault();const f=Object.fromEntries(new FormData(e.target));await api('/api/projects/'+app.projectId+'/risks',{method:'POST',body:f});toast('Risk added');e.target.reset();await refreshAll()} async function closeRisk(id){await api('/api/risks/'+id,{method:'PATCH',body:{status:'Closed',approvalStatus:'Accepted'}});toast('Risk closed');await refreshAll()} async function deleteRisk(id){await api('/api/risks/'+id,{method:'DELETE'});toast('Risk deleted');await refreshAll()} function renderMitigations(){const rows=app.dashboard.mitigations;document.getElementById('mitigations').innerHTML='<div class="two"><div class="panel"><h2>Mitigations</h2><div class="scroll"><table><thead><tr><th>Action</th><th>Risk</th><th>Status</th><th>Progress</th><th>Actions</th></tr></thead><tbody>'+rows.map(m=>'<tr><td>'+esc(m.title)+'</td><td>'+esc(m.riskTitle||m.riskId)+'</td><td>'+m.status+'</td><td>'+m.progressPercent+'%</td><td><button data-action="finish-mitigation" data-id="'+m.id+'">Done</button></td></tr>').join('')+'</tbody></table></div></div><div class="panel"><h2>Add mitigation</h2><form onsubmit="createMitigation(event)" class="form-grid"><select name="riskId">'+app.risks.map(r=>'<option value="'+r.id+'">'+esc(r.title)+'</option>').join('')+'</select><input name="title" placeholder="Mitigation title" required><input name="progressPercent" type="number" min="0" max="100" value="50"><input name="effectivenessPercent" type="number" min="0" max="100" value="60"><input name="evidenceTitle" placeholder="Evidence title"><button>Add mitigation</button></form></div></div>'} async function createMitigation(e){e.preventDefault();const f=Object.fromEntries(new FormData(e.target));const riskId=f.riskId;delete f.riskId;await api('/api/risks/'+riskId+'/mitigations',{method:'POST',body:f});toast('Mitigation added');e.target.reset();await refreshAll()} async function finishMitigation(id){await api('/api/mitigations/'+id,{method:'PATCH',body:{status:'Done',progressPercent:100}});toast('Mitigation completed');await refreshAll()} function renderGate(){document.getElementById('gate').innerHTML='<div class="panel"><h2>Deployment gate</h2>'+cards()+'<button onclick="evaluateGate()">Evaluate and persist gate</button></div><div class="panel"><h2>Gate history</h2><div id="gateHistory">Loading</div></div>';loadGates()} async function loadGates(){const g=(await api('/api/projects/'+app.projectId+'/gate/history')).gates;document.getElementById('gateHistory').innerHTML='<table><thead><tr><th>Status</th><th>Summary</th><th>Decisions</th><th>Action</th></tr></thead><tbody>'+g.map(x=>'<tr><td>'+x.status+'</td><td>'+esc(x.summary)+'</td><td>'+x.decisions.length+'</td><td><button data-action="review-gate" data-id="'+x.id+'">Needs changes</button></td></tr>').join('')+'</tbody></table>'} async function evaluateGate(){await api('/api/projects/'+app.projectId+'/gate/evaluate',{method:'POST'});toast('Gate evaluated');await refreshAll()} async function reviewGate(id){await api('/api/gates/'+id+'/decisions',{method:'POST',body:{decision:'NEEDS_CHANGES',reason:'Local SaaS review'}});toast('Decision recorded');await loadGates()} function renderReports(){document.getElementById('reports').innerHTML='<div class="panel"><h2>Reports and exports</h2><p><a class="btn" href="/api/projects/'+app.projectId+'/reports/executive.html" target="_blank">Executive HTML</a> <a class="btn secondary" href="/api/projects/'+app.projectId+'/reports/risk-register.csv">Risk CSV</a> <a class="btn secondary" href="/api/projects/'+app.projectId+'/reports/full" target="_blank">Full JSON</a> <a class="btn secondary" href="/api/projects/'+app.projectId+'/reports/audit" target="_blank">Audit JSON</a></p></div>'} function renderIntegrations(){document.getElementById('integrations').innerHTML='<div class="panel"><h2>Project-management integrations</h2><table><thead><tr><th>Provider</th><th>Status</th><th>Mode</th><th>Action</th></tr></thead><tbody>'+app.state.integrations.map(i=>'<tr><td>'+i.provider+'</td><td>'+i.connectionStatus+'</td><td>'+i.authMode+'</td><td><button data-action="sync-integration" data-id="'+i.id+'">Simulate sync</button></td></tr>').join('')+'</tbody></table></div>'} async function syncIntegration(id){await api('/api/integrations/'+id+'/sync',{method:'POST'});toast('Integration sync simulated');await refreshAll()} function renderAdmin(){document.getElementById('admin').innerHTML='<div class="two"><div class="panel"><h2>Users and roles</h2><table><thead><tr><th>User</th><th>Email</th><th>Role</th></tr></thead><tbody>'+app.state.users.map(u=>'<tr><td>'+esc(u.displayName)+'</td><td>'+u.email+'</td><td>'+u.role+'</td></tr>').join('')+'</tbody></table></div><div class="panel"><h2>Invite user</h2><form onsubmit="inviteUser(event)" class="form-grid"><input name="email" type="email" placeholder="email@example.com" required><select name="roleCode"><option value="viewer">Viewer</option><option value="risk_owner">Risk Owner</option><option value="project_manager">Project Manager</option><option value="consultant">Consultant</option></select><button>Invite</button></form><h2>Billing</h2><p><b>Plan:</b> '+app.state.subscriptions[0].planCode+' / '+app.state.subscriptions[0].status+'</p><button onclick="activateBilling()">Activate manual billing</button></div></div><div class="panel"><h2>Audit trail</h2><div class="scroll"><table><thead><tr><th>Action</th><th>Entity</th><th>When</th></tr></thead><tbody>'+app.state.auditEvents.map(a=>'<tr><td>'+a.action+'</td><td>'+a.entityType+' '+a.entityId+'</td><td>'+a.createdAt+'</td></tr>').join('')+'</tbody></table></div></div>'} async function inviteUser(e){e.preventDefault();const f=Object.fromEntries(new FormData(e.target));await api('/api/auth/invite',{method:'POST',body:{organizationId:'org-demo',...f}});toast('Invitation created');e.target.reset();await refreshAll()} async function activateBilling(){await api('/api/billing/subscription',{method:'PATCH',body:{status:'ACTIVE',planCode:'team'}});toast('Billing activated');await refreshAll()} document.addEventListener('click',event=>{const button=event.target.closest('[data-action]');if(!button)return;const action=button.dataset.action;if(action==='refresh')refreshAll();if(action==='tab')setTab(button.dataset.target);if(action==='close-risk')closeRisk(button.dataset.id);if(action==='delete-risk')deleteRisk(button.dataset.id);if(action==='finish-mitigation')finishMitigation(button.dataset.id);if(action==='review-gate')reviewGate(button.dataset.id);if(action==='sync-integration')syncIntegration(button.dataset.id)}); initTabs();refreshAll().catch(err=>{document.body.innerHTML='<main><h1>Load error</h1><pre>'+esc(err.message)+'</pre></main>'}); </script></body></html>`; } async function handleApi(req, res, pathname) { const method = req.method; const parts = pathname.split("/").filter(Boolean); const url = new URL(req.url, `http://${req.headers.host || "localhost"}`); if (method === "GET" && pathname === "/api/health") return sendJson(res, 200, { service: "COSMIC AI-Risk SaaS Local", version: "0.2.0", status: "ok" }); if (method === "GET" && pathname === "/api/ready") return sendJson(res, 200, { ok: true, mode: "dependency-free-local", database: "prototype-seed-memory", postgresContainer: "saas-app-postgres-1" }); if (method === "GET" && pathname === "/api/dashboard") return sendJson(res, 200, legacyDashboardPayload()); if (method === "GET" && pathname === "/api/access/me") return sendJson(res, 200, legacyAccessPayload()); if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "integrations") return sendJson(res, 200, { integrations: legacyIntegrations() }); if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "integration-mappings") return sendJson(res, 200, { mappings: state.mappings }); if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "sync-runs") return sendJson(res, 200, { syncRuns: state.syncRuns }); if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "gate" && parts[4] === "history") return sendJson(res, 200, { gates: state.gates.filter(g => g.projectId === "project-demo").map(legacyGate) }); if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "audit") return sendJson(res, 200, { auditEvents: state.auditEvents.filter(a => !parts[2] || a.projectId === "project-demo" || a.projectId === parts[2]) }); if (method === "PATCH" && parts[0] === "api" && parts[1] === "gate-criteria") { const b = await parseBody(req); for (const gate of state.gates) { const criterion = (gate.criteria || []).find((item, index) => (item.id || `${gate.id}-criterion-${index + 1}`) === parts[2]); if (criterion) { Object.assign(criterion, b); audit("gate.criterion.update", "GateCriterion", parts[2], gate.projectId, criterion); return sendJson(res, 200, { criterion: legacyCriterion(criterion, 0, gate.id) }); } } return sendJson(res, 404, { error: "Gate criterion not found" }); } if (method === "GET" && parts[0] === "api" && parts[1] === "oauth") return sendJson(res, 200, { authorizeUrl: "/api/health", provider: parts[2], mode: "local-demo" }); if (method === "POST" && parts[0] === "api" && parts[1] === "integrations" && parts[3] === "live" && parts[4] === "test") return sendJson(res, 200, { dryRun: true, message: "Live connector test is disabled in local demo mode." }); if (method === "POST" && parts[0] === "api" && parts[1] === "integrations" && parts[3] === "live" && parts[4] === "sync") return sendJson(res, 200, { dryRun: true, message: "Live connector sync is disabled in local demo mode." }); if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "reports") { const payload = legacyDashboardPayload(); const type = parts[4] || "full.json"; if (type === "executive.html") return sendText(res, 200, `<html><body><h1>${payload.dashboard.project.name}</h1><p>Overall score: ${payload.dashboard.summary.overallScore}</p><p>Gate: ${payload.dashboard.summary.gateStatus}</p><p>Open risks: ${payload.dashboard.summary.openRisks}</p></body></html>`, "text/html; charset=utf-8"); if (type === "risk-register.csv") return sendText(res, 200, csv(payload.dashboard.scoredRisks, ["id","title","dimension","status","normalizedScore","residualScore"]), "text/csv; charset=utf-8"); if (type === "mitigations.csv") return sendText(res, 200, csv(payload.dashboard.mitigations, ["id","title","riskId","status","progress_percent","effectiveness_percent"]), "text/csv; charset=utf-8"); if (type === "gate") return sendJson(res, 200, { report: { gate: payload.dashboard.gate, gates: state.gates.map(legacyGate) } }); if (type === "audit") return sendJson(res, 200, { report: { auditEvents: state.auditEvents } }); return sendJson(res, 200, { reportType: type, data: payload.data, dashboard: payload.dashboard, report: payload }); } if (method === "GET" && pathname === "/api/local/state") return sendJson(res, 200, fullState()); if (method === "GET" && pathname === "/api/auth/me") return sendJson(res, 200, { actor: { ...publicUser(state.users[0]), memberships: [{ organizationId: "org-demo", organizationName: state.organizations[0].name, role: "owner", permissions: ROLE_PERMISSIONS.owner, status: "ACTIVE" }] } }); if (method === "POST" && pathname === "/api/auth/invite") { const b = await parseBody(req); const inv = { id: makeId("invite"), organizationId: b.organizationId || "org-demo", email: b.email, roleCode: b.roleCode || "viewer", token: makeId("token"), expiresAt: new Date(Date.now()+7*864e5).toISOString(), acceptedAt: null }; state.invitations.unshift(inv); audit("invite.create", "Invitation", inv.id, null, inv); return sendJson(res, 201, { invitation: inv }); } if (method === "POST" && pathname === "/api/auth/accept-invite") { const b = await parseBody(req); const inv = state.invitations.find(i => i.token === b.token); if (!inv) return sendJson(res, 400, { error: "Invitation is invalid or expired." }); const u = { id: makeId("user"), organizationId: inv.organizationId, email: inv.email, displayName: b.displayName || inv.email, role: inv.roleCode, permissions: ROLE_PERMISSIONS[inv.roleCode] || ROLE_PERMISSIONS.viewer, status: "ACTIVE" }; inv.acceptedAt = now(); state.users.push(u); audit("invite.accept", "User", u.id, null, publicUser(u)); return sendJson(res, 200, { user: publicUser(u), membership: { organizationId: inv.organizationId, status: "ACTIVE", role: inv.roleCode } }); } if (method === "GET" && pathname === "/api/organizations") return sendJson(res, 200, { organizations: state.organizations }); if (method === "POST" && pathname === "/api/organizations") { const b = await parseBody(req); const org = { id: makeId("org"), name: b.name, slug: b.slug || slug(b.name), status: "TRIAL", planCode: b.planCode || "pilot", manualBilling: true, allowedDomains: [], dataRetentionDays: 365 }; state.organizations.push(org); audit("organization.create", "Organization", org.id, null, org); return sendJson(res, 201, { organization: org }); } if (method === "GET" && pathname === "/api/workspaces") return sendJson(res, 200, { workspaces: state.workspaces.filter(w => !url.searchParams.get("organizationId") || w.organizationId === url.searchParams.get("organizationId")) }); if (method === "POST" && pathname === "/api/workspaces") { const b = await parseBody(req); const ws = { id: makeId("workspace"), organizationId: b.organizationId || "org-demo", name: b.name, slug: b.slug || slug(b.name), type: b.type || "B2B_TEAM", status: "TRIAL" }; state.workspaces.push(ws); audit("workspace.create", "Workspace", ws.id, null, ws); return sendJson(res, 201, { workspace: ws }); } if (method === "PATCH" && parts[0] === "api" && parts[1] === "workspaces" && parts.length === 3) { const ws = state.workspaces.find(w => w.id === parts[2]); if (!ws) return sendJson(res, 404, { error: "Workspace not found" }); Object.assign(ws, await parseBody(req)); audit("workspace.update", "Workspace", ws.id, null, ws); return sendJson(res, 200, { workspace: ws }); } if (method === "GET" && pathname === "/api/users") return sendJson(res, 200, { users: state.users.map(publicUser) }); if (method === "GET" && pathname === "/api/roles") return sendJson(res, 200, { roles: Object.entries(ROLE_PERMISSIONS).map(([code, permissions]) => ({ code, permissions })) }); if (method === "GET" && parts[0] === "api" && parts[1] === "workspaces" && parts[3] === "projects") return sendJson(res, 200, { projects: state.projects.filter(p => p.workspaceId === parts[2]) }); if (method === "POST" && parts[0] === "api" && parts[1] === "workspaces" && parts[3] === "projects") { const b = await parseBody(req); const p = { id: makeId("project"), workspaceId: parts[2], name: b.name, subtitle: b.subtitle || "COSMIC AI risk workspace", projectType: b.projectType || "AI-Enabler", currentLifecyclePhase: b.currentLifecyclePhase || "Needs identification", riskAppetite: Number(b.riskAppetite || 50), assessmentDate: now().slice(0,10), status: "Active", ownerDisplayName: "COSMIC Platform Owner", description: b.description || "", thresholds: b.thresholds || {}, reviews: b.reviews || {}, lifecyclePhases: [], risks: [], indicators: [], experiments: [] }; state.projects.push(p); audit("project.create", "Project", p.id, p.id, p); return sendJson(res, 201, { project: p }); } if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "dashboard") { const d = getDashboard(parts[2]); return d ? sendJson(res, 200, { dashboard: d }) : sendJson(res, 404, { error: "Project not found" }); } if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "risks") { const d = getDashboard(parts[2]); return d ? sendJson(res, 200, { risks: d.scoredRisks }) : sendJson(res, 404, { error: "Project not found" }); } if (method === "POST" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "risks") { const b = await parseBody(req); const p = getProject(parts[2]); if (!p) return sendJson(res, 404, { error: "Project not found" }); const r = { id: makeId("risk"), projectId: p.id, ownerDisplayName: b.ownerDisplayName || "COSMIC Platform Owner", title: b.title, description: b.description || "", dimension: b.dimension || "Technical", domain: b.domain || "Technical risks", lifecyclePhase: b.lifecyclePhase || p.currentLifecyclePhase || "Needs identification", probability: Number(b.probability || 3), impact: Number(b.impact || 3), detectability: Number(b.detectability || 3), status: riskStatus(b.status), approvalStatus: approvalStatus(b.approvalStatus), dueDate: b.dueDate || null, evidenceSummary: b.evidenceSummary || "", mitigations: [], evidence: [] }; p.risks.push(r); audit("risk.create", "Risk", r.id, p.id, r); return sendJson(res, 201, { risk: scoreRisk(r) }); } if ((method === "PATCH" || method === "DELETE") && parts[0] === "api" && parts[1] === "risks" && parts.length === 3) { const found = getRisk(parts[2]); if (!found.risk) return sendJson(res, 404, { error: "Risk not found" }); if (method === "DELETE") { found.project.risks = found.project.risks.filter(r => r.id !== parts[2]); audit("risk.delete", "Risk", parts[2], found.project.id); return sendJson(res, 200, { deleted: true }); } const b = await parseBody(req); Object.assign(found.risk, b, { status: b.status ? riskStatus(b.status) : found.risk.status, approvalStatus: b.approvalStatus ? approvalStatus(b.approvalStatus) : found.risk.approvalStatus }); audit("risk.update", "Risk", found.risk.id, found.project.id, found.risk); return sendJson(res, 200, { risk: scoreRisk(found.risk) }); } if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "mitigations") return sendJson(res, 200, { mitigations: getAllMitigations(parts[2]) }); if (method === "POST" && parts[0] === "api" && parts[1] === "risks" && parts[3] === "mitigations") { const b = await parseBody(req); const found = getRisk(parts[2]); if (!found.risk) return sendJson(res, 404, { error: "Risk not found" }); const m = { id: makeId("mit"), riskId: found.risk.id, ownerDisplayName: b.ownerDisplayName || "COSMIC Platform Owner", title: b.title, description: b.description || "", status: mitigationStatus(b.status), progressPercent: Number(b.progressPercent || b.progress_percent || 0), effectivenessPercent: Number(b.effectivenessPercent || b.effectiveness_percent || 0), dueDate: b.dueDate || null, sequence: found.risk.mitigations.length + 1, evidence: [] }; found.risk.mitigations.push(m); if (b.evidenceTitle || b.evidenceUri) m.evidence.push({ id: makeId("evd"), title: b.evidenceTitle || b.evidenceUri, uri: b.evidenceUri || null, type: b.evidenceType || "Review" }); audit("mitigation.create", "Mitigation", m.id, found.project.id, m); return sendJson(res, 201, { mitigation: m }); } if ((method === "PATCH" || method === "DELETE") && parts[0] === "api" && parts[1] === "mitigations" && parts.length === 3) { const found = getMitigation(parts[2]); if (!found.mitigation) return sendJson(res, 404, { error: "Mitigation not found" }); if (method === "DELETE") { found.risk.mitigations = found.risk.mitigations.filter(m => m.id !== parts[2]); audit("mitigation.delete", "Mitigation", parts[2], found.project.id); return sendJson(res, 200, { deleted: true }); } const b = await parseBody(req); Object.assign(found.mitigation, b, { status: b.status ? mitigationStatus(b.status) : found.mitigation.status }); audit("mitigation.update", "Mitigation", found.mitigation.id, found.project.id, found.mitigation); return sendJson(res, 200, { mitigation: found.mitigation }); } if (method === "POST" && parts[0] === "api" && parts[1] === "mitigations" && parts[3] === "evidence") { const b = await parseBody(req); const found = getMitigation(parts[2]); if (!found.mitigation) return sendJson(res, 404, { error: "Mitigation not found" }); const ev = { id: makeId("evd"), title: b.title || b.evidenceTitle || b.uri, uri: b.uri || b.evidenceUri || null, type: b.type || "Review" }; found.mitigation.evidence.push(ev); audit("evidence.create", "Evidence", ev.id, found.project.id, ev); return sendJson(res, 201, { evidence: ev }); } if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "gate" && parts[4] === "history") return sendJson(res, 200, { gates: state.gates.filter(g => g.projectId === parts[2]) }); if (method === "POST" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "gate" && parts[4] === "evaluate") { const d = getDashboard(parts[2]); if (!d) return sendJson(res, 404, { error: "Project not found" }); const g = { id: makeId("gate"), projectId: parts[2], status: d.gate.status, summary: d.gate.message, criteria: d.gate.criteria, decisions: [], evaluatedAt: now() }; state.gates.unshift(g); audit("gate.evaluate", "Gate", g.id, parts[2], g); return sendJson(res, 201, { gate: legacyGate(g), persistedGate: legacyGate(g) }); } if (method === "POST" && parts[0] === "api" && parts[1] === "gates" && parts[3] === "decisions") { const b = await parseBody(req); const g = state.gates.find(x => x.id === parts[2]); if (!g) return sendJson(res, 404, { error: "Gate not found" }); const d = { id: makeId("decision"), gateId: g.id, decision: b.decision || "NEEDS_CHANGES", reviewerName: b.reviewerName || "Governance Reviewer", reason: b.reason || "", decidedAt: now() }; g.decisions.push(d); audit("gate.decision", "GateDecision", d.id, g.projectId, d); return sendJson(res, 201, { decision: d }); } if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "audit") return sendJson(res, 200, { auditEvents: state.auditEvents.filter(a => a.projectId === parts[2]) }); if (method === "GET" && parts[0] === "api" && parts[1] === "projects" && parts[3] === "reports") { const d = getDashboard(parts[2]); if (!d) return sendJson(res, 404, { error: "Project not found" }); const type = parts[4] || "full"; state.reportExports.unshift({ id: makeId("report"), projectId: parts[2], reportType: type, createdAt: now() }); if (type === "executive.html") return sendText(res, 200, `<html><body><h1>${d.project.name}</h1><p>Overall score: ${d.summary.overallScore}</p><p>Gate: ${d.summary.gateStatus}</p><p>Open risks: ${d.summary.openRisks}</p></body></html>`, "text/html; charset=utf-8"); if (type === "risk-register.csv") return sendText(res, 200, csv(d.scoredRisks, ["id","title","dimension","status","normalizedScore","residualScore"]), "text/csv; charset=utf-8"); return sendJson(res, 200, { reportType: type, dashboard: d, auditEvents: state.auditEvents.filter(a => a.projectId === parts[2]) }); } if (method === "GET" && pathname === "/api/integration-providers") return sendJson(res, 200, { providers: listSupportedProviders() }); if (method === "GET" && parts[0] === "api" && parts[1] === "workspaces" && parts[3] === "integrations") return sendJson(res, 200, { integrations: state.integrations.filter(i => i.workspaceId === parts[2]) }); if (method === "POST" && parts[0] === "api" && parts[1] === "integrations" && parts[3] === "sync") { const i = state.integrations.find(x => x.id === parts[2]); if (!i) return sendJson(res, 404, { error: "Integration not found" }); const p = state.projects.find(x => x.workspaceId === i.workspaceId); const d = getDashboard(p.id); let created = 0; d.scoredRisks.forEach(r => { if (!state.mappings.find(m => m.integrationId === i.id && m.localEntityId === r.id)) { created += 1; state.mappings.push({ id: makeId("map"), integrationId: i.id, projectId: p.id, provider: i.provider, localEntityType: "Risk", localEntityId: r.id, localTitle: r.title, externalItemType: "Work item", externalItemId: `${i.provider}-${r.id}`, externalStatus: riskStatusToExternalStatus(i.provider, r), syncStatus: "Synced", lastSyncedAt: now() }); } }); const run = { id: makeId("sync"), integrationId: i.id, projectId: p.id, provider: i.provider, status: "Completed", createdCount: created, updatedCount: d.scoredRisks.length - created, failedCount: 0, summary: `${i.provider} simulated sync completed.`, startedAt: now(), finishedAt: now() }; i.connectionStatus = "CONNECTED"; i.lastSyncAt = now(); state.syncRuns.unshift(run); audit("integration.sync", "IntegrationSyncRun", run.id, p.id, run); return sendJson(res, 200, { integration: i, syncRun: run, mappings: state.mappings.filter(m => m.integrationId === i.id) }); } if (method === "GET" && parts[0] === "api" && parts[1] === "workspaces" && parts[3] === "integration-mappings") return sendJson(res, 200, { mappings: state.mappings.filter(m => state.integrations.find(i => i.id === m.integrationId && i.workspaceId === parts[2])) }); if (method === "GET" && parts[0] === "api" && parts[1] === "workspaces" && parts[3] === "sync-runs") return sendJson(res, 200, { syncRuns: state.syncRuns.filter(s => state.projects.find(p => p.id === s.projectId && p.workspaceId === parts[2])) }); if (method === "GET" && pathname === "/api/billing/subscription") return sendJson(res, 200, { subscription: state.subscriptions[0], entitlements: { active: ["TRIALING","ACTIVE"].includes(state.subscriptions[0].status), limits: state.subscriptions[0].limits } }); if (method === "PATCH" && pathname === "/api/billing/subscription") { Object.assign(state.subscriptions[0], await parseBody(req)); audit("billing.update", "Subscription", state.subscriptions[0].id, null, state.subscriptions[0]); return sendJson(res, 200, { subscription: state.subscriptions[0] }); } if (pathname === "/api/billing/checkout" || pathname === "/api/billing/portal" || pathname === "/api/billing/webhook") return sendJson(res, 200, { mode: "manual", message: "Stripe boundary placeholder active in local mode." }); return sendJson(res, 404, { error: "API route not found", pathname }); } const server = http.createServer((req, res) => { const pathname = new URL(req.url, `http://${req.headers.host || "localhost"}`).pathname; const publicDir = path.join(__dirname, "..", "public"); if (req.method === "OPTIONS") return sendJson(res, 204, {}); if (pathname === "/" || pathname === "/dashboard") return sendFile(res, path.join(publicDir, "index.html"), "text/html; charset=utf-8"); if (pathname === "/saas-local") return sendText(res, 200, appHtml(), "text/html; charset=utf-8"); if (["/styles.css","/app.js","/risk-engine.js","/oauth-trello-token.html"].includes(pathname)) return sendFile(res, path.join(publicDir, pathname.slice(1)), staticType(pathname)); if (pathname.startsWith("/data/")) return sendFile(res, path.join(__dirname, "..", pathname), staticType(pathname)); if (pathname.startsWith("/api/")) return handleApi(req, res, pathname).catch(e => sendJson(res, e.status || 500, { error: e.message })); return sendText(res, 404, "Not found"); }); if (require.main === module) server.listen(PORT, () => console.log(`COSMIC AI-Risk SaaS local platform running at http://localhost:${PORT}`)); module.exports = { server, state };
Save
cmd:
run