Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
// Atlas InfoSec Action Plans Routes
|
2026-05-06 11:44:17 -06:00
|
|
|
// Proxies CRUD operations to the Atlas API and maintains a local cache
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
// for fast badge rendering on the ReportingPage.
|
|
|
|
|
|
|
|
|
|
const express = require('express');
|
2026-05-06 11:44:17 -06:00
|
|
|
const pool = require('../db');
|
|
|
|
|
const { requireAuth, requireGroup } = require('../middleware/auth');
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
const logAudit = require('../helpers/auditLog');
|
|
|
|
|
const { isConfigured, atlasGet, atlasPut, atlasPatch, atlasPost } = require('../helpers/atlasApi');
|
|
|
|
|
|
2026-05-05 11:04:53 -06:00
|
|
|
const fs = require('fs');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
const VALID_PLAN_TYPES = ['decommission', 'remediation', 'false_positive', 'risk_acceptance', 'scan_exclusion'];
|
|
|
|
|
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
// Diagnostic log helper
|
2026-05-05 11:04:53 -06:00
|
|
|
function syncLog(msg) {
|
|
|
|
|
const line = `${new Date().toISOString()} ${msg}\n`;
|
|
|
|
|
try { fs.appendFileSync(path.join(__dirname, '..', 'atlas-sync-debug.log'), line); } catch (_) { /* ignore */ }
|
|
|
|
|
console.log(msg);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 17:30:06 +00:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Pure aggregation function — exported for testability
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
function aggregateAtlasMetrics(rows) {
|
|
|
|
|
const result = {
|
|
|
|
|
totalHosts: rows.length,
|
|
|
|
|
hostsWithPlans: 0,
|
|
|
|
|
hostsWithoutPlans: 0,
|
|
|
|
|
plansByType: {},
|
|
|
|
|
plansByStatus: {},
|
|
|
|
|
totalPlans: 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const row of rows) {
|
2026-05-06 11:44:17 -06:00
|
|
|
if (row.has_action_plan === true || row.has_action_plan === 1) {
|
2026-04-24 17:30:06 +00:00
|
|
|
result.hostsWithPlans++;
|
|
|
|
|
} else {
|
|
|
|
|
result.hostsWithoutPlans++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let plans;
|
|
|
|
|
try {
|
|
|
|
|
plans = JSON.parse(row.plans_json);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Array.isArray(plans)) continue;
|
|
|
|
|
|
|
|
|
|
for (const plan of plans) {
|
|
|
|
|
result.totalPlans++;
|
|
|
|
|
if (plan.plan_type) {
|
|
|
|
|
result.plansByType[plan.plan_type] = (result.plansByType[plan.plan_type] || 0) + 1;
|
|
|
|
|
}
|
|
|
|
|
if (plan.status) {
|
|
|
|
|
result.plansByStatus[plan.status] = (result.plansByStatus[plan.status] || 0) + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Router factory
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-05-06 11:44:17 -06:00
|
|
|
function createAtlasRouter() {
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
const router = express.Router();
|
|
|
|
|
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
/**
|
|
|
|
|
* GET /metrics
|
|
|
|
|
*
|
|
|
|
|
* Returns aggregated Atlas action plan metrics from the local cache.
|
2026-06-12 12:38:45 -06:00
|
|
|
* Accepts optional `teams` query parameter to scope metrics to hosts
|
|
|
|
|
* belonging to specific BUs (via JOIN on ivanti_findings).
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
*
|
2026-06-12 12:38:45 -06:00
|
|
|
* @query {string} [teams] - Comma-separated team names (e.g. 'STEAM,ACCESS-ENG')
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
* @returns {Object} 200 - { totalHosts, hostsWithPlans, hostsWithoutPlans, plansByType, plansByStatus, totalPlans }
|
|
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
* @returns {Object} 500 - { error } on database failure
|
|
|
|
|
*/
|
2026-05-06 11:44:17 -06:00
|
|
|
router.get('/metrics', requireAuth(), async (req, res) => {
|
2026-04-24 17:30:06 +00:00
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2026-06-12 12:38:45 -06:00
|
|
|
const teamsParam = req.query.teams;
|
|
|
|
|
let rows;
|
|
|
|
|
|
|
|
|
|
if (teamsParam) {
|
|
|
|
|
const teams = teamsParam.split(',').map(t => t.trim()).filter(Boolean);
|
|
|
|
|
if (teams.length > 0) {
|
|
|
|
|
const patterns = teams.map(t => `%${t}%`);
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`SELECT a.has_action_plan, a.plans_json
|
|
|
|
|
FROM atlas_action_plans_cache a
|
|
|
|
|
INNER JOIN (
|
|
|
|
|
SELECT DISTINCT host_id FROM ivanti_findings
|
|
|
|
|
WHERE bu_ownership ILIKE ANY($1::text[])
|
2026-06-12 13:25:00 -06:00
|
|
|
) f ON a.host_id = f.host_id
|
|
|
|
|
WHERE a.atlas_known = true`,
|
2026-06-12 12:38:45 -06:00
|
|
|
[patterns]
|
|
|
|
|
);
|
|
|
|
|
rows = result.rows;
|
|
|
|
|
} else {
|
|
|
|
|
const result = await pool.query(
|
2026-06-12 13:25:00 -06:00
|
|
|
`SELECT has_action_plan, plans_json FROM atlas_action_plans_cache WHERE atlas_known = true`
|
2026-06-12 12:38:45 -06:00
|
|
|
);
|
|
|
|
|
rows = result.rows;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const result = await pool.query(
|
2026-06-12 13:25:00 -06:00
|
|
|
`SELECT has_action_plan, plans_json FROM atlas_action_plans_cache WHERE atlas_known = true`
|
2026-06-12 12:38:45 -06:00
|
|
|
);
|
|
|
|
|
rows = result.rows;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 17:30:06 +00:00
|
|
|
const metrics = aggregateAtlasMetrics(rows);
|
|
|
|
|
res.json(metrics);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas] Error fetching metrics:', err.message);
|
|
|
|
|
res.status(500).json({ error: 'Failed to fetch Atlas metrics.' });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
/**
|
|
|
|
|
* GET /status
|
|
|
|
|
*
|
2026-06-12 12:38:45 -06:00
|
|
|
* Returns atlas_action_plans_cache contents for status display.
|
|
|
|
|
* Accepts optional `teams` query parameter to scope results to hosts
|
|
|
|
|
* belonging to specific BUs (via JOIN on ivanti_findings).
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
*
|
2026-06-12 12:38:45 -06:00
|
|
|
* @query {string} [teams] - Comma-separated team names (e.g. 'STEAM,ACCESS-ENG')
|
2026-06-12 13:25:00 -06:00
|
|
|
* @returns {Array} 200 - Array of { host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at }
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
* @returns {Object} 500 - { error } on database failure
|
|
|
|
|
*/
|
2026-05-06 11:44:17 -06:00
|
|
|
router.get('/status', requireAuth(), async (req, res) => {
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2026-06-12 12:38:45 -06:00
|
|
|
const teamsParam = req.query.teams;
|
|
|
|
|
let rows;
|
|
|
|
|
|
|
|
|
|
if (teamsParam) {
|
|
|
|
|
const teams = teamsParam.split(',').map(t => t.trim()).filter(Boolean);
|
|
|
|
|
if (teams.length > 0) {
|
|
|
|
|
const patterns = teams.map(t => `%${t}%`);
|
|
|
|
|
const result = await pool.query(
|
2026-06-12 13:25:00 -06:00
|
|
|
`SELECT a.host_id, a.has_action_plan, a.plan_count, a.plans_json, a.atlas_known, a.synced_at
|
2026-06-12 12:38:45 -06:00
|
|
|
FROM atlas_action_plans_cache a
|
|
|
|
|
INNER JOIN (
|
|
|
|
|
SELECT DISTINCT host_id FROM ivanti_findings
|
|
|
|
|
WHERE bu_ownership ILIKE ANY($1::text[])
|
|
|
|
|
) f ON a.host_id = f.host_id`,
|
|
|
|
|
[patterns]
|
|
|
|
|
);
|
|
|
|
|
rows = result.rows;
|
|
|
|
|
} else {
|
|
|
|
|
const result = await pool.query(
|
2026-06-12 13:25:00 -06:00
|
|
|
`SELECT host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at FROM atlas_action_plans_cache`
|
2026-06-12 12:38:45 -06:00
|
|
|
);
|
|
|
|
|
rows = result.rows;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const result = await pool.query(
|
2026-06-12 13:25:00 -06:00
|
|
|
`SELECT host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at FROM atlas_action_plans_cache`
|
2026-06-12 12:38:45 -06:00
|
|
|
);
|
|
|
|
|
rows = result.rows;
|
|
|
|
|
}
|
|
|
|
|
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.json(rows);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas] Error fetching status:', err.message);
|
|
|
|
|
res.status(500).json({ error: 'Failed to fetch Atlas status.' });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
/**
|
|
|
|
|
* POST /sync
|
|
|
|
|
*
|
|
|
|
|
* Syncs action plan data from Atlas for all hosts found in ivanti_findings.
|
|
|
|
|
* Fetches plans per host in batches of 5 and upserts into the local cache.
|
2026-06-12 13:25:00 -06:00
|
|
|
* Scopes to the provided teams or falls back to IVANTI_MANAGED_BUS.
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
* Requires Admin or Standard_User group.
|
|
|
|
|
*
|
2026-06-12 13:25:00 -06:00
|
|
|
* @query {string} [teams] - Comma-separated team names to scope sync (e.g. 'STEAM,ACCESS-ENG')
|
|
|
|
|
* @param {Object} [req.body]
|
|
|
|
|
* @param {string} [req.body.teams] - Comma-separated team names (alternative to query param)
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
* @returns {Object} 200 - { synced, withPlans, failed }
|
|
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
* @returns {Object} 500 - { error } on unexpected failure
|
|
|
|
|
*/
|
2026-05-06 11:44:17 -06:00
|
|
|
router.post('/sync', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2026-06-12 12:38:45 -06:00
|
|
|
// Scope sync to the user's active teams if provided, otherwise sync only
|
|
|
|
|
// findings from managed BUs (IVANTI_MANAGED_BUS) to avoid polluting cache
|
|
|
|
|
// with "no plan" entries for BUs not covered by Atlas.
|
|
|
|
|
const teamsParam = req.query.teams || req.body.teams || '';
|
|
|
|
|
const managedBUs = (process.env.IVANTI_MANAGED_BUS || 'NTS-AEO-ACCESS-ENG,NTS-AEO-STEAM')
|
|
|
|
|
.split(',').map(b => b.trim()).filter(Boolean);
|
|
|
|
|
|
|
|
|
|
let findingsRows;
|
|
|
|
|
if (teamsParam) {
|
|
|
|
|
const teams = teamsParam.split(',').map(t => t.trim()).filter(Boolean);
|
|
|
|
|
if (teams.length > 0) {
|
|
|
|
|
const patterns = teams.map(t => `%${t}%`);
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`SELECT DISTINCT host_id FROM ivanti_findings
|
|
|
|
|
WHERE host_id IS NOT NULL AND host_id > 0
|
|
|
|
|
AND bu_ownership ILIKE ANY($1::text[])`,
|
|
|
|
|
[patterns]
|
|
|
|
|
);
|
|
|
|
|
findingsRows = result.rows;
|
|
|
|
|
} else {
|
|
|
|
|
// No valid teams — fall back to managed BUs
|
|
|
|
|
const patterns = managedBUs.map(b => `%${b}%`);
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`SELECT DISTINCT host_id FROM ivanti_findings
|
|
|
|
|
WHERE host_id IS NOT NULL AND host_id > 0
|
|
|
|
|
AND bu_ownership ILIKE ANY($1::text[])`,
|
|
|
|
|
[patterns]
|
|
|
|
|
);
|
|
|
|
|
findingsRows = result.rows;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// No teams specified — default to managed BUs only
|
|
|
|
|
const patterns = managedBUs.map(b => `%${b}%`);
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`SELECT DISTINCT host_id FROM ivanti_findings
|
|
|
|
|
WHERE host_id IS NOT NULL AND host_id > 0
|
|
|
|
|
AND bu_ownership ILIKE ANY($1::text[])`,
|
|
|
|
|
[patterns]
|
|
|
|
|
);
|
|
|
|
|
findingsRows = result.rows;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
const hostIds = findingsRows.map(r => r.host_id);
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
|
|
|
|
|
if (hostIds.length === 0) {
|
|
|
|
|
return res.json({ synced: 0, withPlans: 0, failed: 0 });
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 15:40:51 -06:00
|
|
|
// Build a set of host IDs belonging to managed BUs — these always show the badge
|
|
|
|
|
const managedPatterns = managedBUs.map(b => `%${b}%`);
|
|
|
|
|
let managedHostIds = new Set();
|
|
|
|
|
try {
|
|
|
|
|
const { rows: managedRows } = await pool.query(
|
|
|
|
|
`SELECT DISTINCT host_id FROM ivanti_findings
|
|
|
|
|
WHERE host_id IS NOT NULL AND host_id > 0
|
|
|
|
|
AND bu_ownership ILIKE ANY($1::text[])`,
|
|
|
|
|
[managedPatterns]
|
|
|
|
|
);
|
|
|
|
|
managedHostIds = new Set(managedRows.map(r => r.host_id));
|
|
|
|
|
} catch (_) { /* non-fatal — fall back to plans-only logic */ }
|
|
|
|
|
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
let synced = 0;
|
|
|
|
|
let withPlans = 0;
|
|
|
|
|
let failed = 0;
|
|
|
|
|
const BATCH_SIZE = 5;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < hostIds.length; i += BATCH_SIZE) {
|
|
|
|
|
const batch = hostIds.slice(i, i + BATCH_SIZE);
|
|
|
|
|
const results = await Promise.allSettled(
|
|
|
|
|
batch.map(async (hostId) => {
|
|
|
|
|
const result = await atlasGet('/hosts/' + hostId + '/action-plans');
|
|
|
|
|
return { hostId, result };
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const settled of results) {
|
|
|
|
|
if (settled.status === 'rejected') {
|
|
|
|
|
failed++;
|
|
|
|
|
console.warn('[Atlas Sync] Request failed for host:', settled.reason?.message || settled.reason);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { hostId, result } = settled.value;
|
2026-06-16 15:40:51 -06:00
|
|
|
const isManagedHost = managedHostIds.has(hostId);
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
|
|
|
|
|
if (result.status >= 200 && result.status < 300) {
|
|
|
|
|
let allPlans = [];
|
|
|
|
|
let activePlans = [];
|
|
|
|
|
try {
|
|
|
|
|
const parsed = JSON.parse(result.body);
|
|
|
|
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
|
|
|
activePlans = Array.isArray(parsed.active) ? parsed.active : [];
|
|
|
|
|
const inactive = Array.isArray(parsed.inactive) ? parsed.inactive : [];
|
|
|
|
|
allPlans = [...activePlans, ...inactive];
|
|
|
|
|
} else if (Array.isArray(parsed)) {
|
|
|
|
|
allPlans = parsed;
|
|
|
|
|
activePlans = parsed;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
allPlans = [];
|
|
|
|
|
activePlans = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const planCount = activePlans.length;
|
2026-05-06 11:44:17 -06:00
|
|
|
const hasActionPlan = planCount > 0;
|
2026-06-16 15:40:51 -06:00
|
|
|
// Atlas "knows" this host if:
|
|
|
|
|
// 1. It returned any plans (active or inactive), OR
|
|
|
|
|
// 2. The host belongs to a managed BU (these should always show the badge)
|
|
|
|
|
// Non-managed BU hosts with empty responses don't get a badge.
|
|
|
|
|
const atlasKnown = allPlans.length > 0 || isManagedHost;
|
2026-05-05 11:04:53 -06:00
|
|
|
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
try {
|
2026-05-06 11:44:17 -06:00
|
|
|
if (!hasActionPlan) {
|
|
|
|
|
const { rows: existingRows } = await pool.query(
|
|
|
|
|
`SELECT has_action_plan, plans_json, synced_at FROM atlas_action_plans_cache WHERE host_id = $1`,
|
2026-05-05 11:04:53 -06:00
|
|
|
[hostId]
|
|
|
|
|
);
|
2026-05-06 11:44:17 -06:00
|
|
|
const existing = existingRows[0];
|
|
|
|
|
if (existing && existing.has_action_plan === true) {
|
2026-05-05 11:04:53 -06:00
|
|
|
let existingPlans = [];
|
|
|
|
|
try { existingPlans = JSON.parse(existing.plans_json || '[]'); } catch (_) {}
|
|
|
|
|
const hasBulkStub = existingPlans.some(p => p.source === 'bulk-create');
|
|
|
|
|
if (hasBulkStub) {
|
2026-05-06 11:44:17 -06:00
|
|
|
const ageMs = Date.now() - new Date(existing.synced_at).getTime();
|
2026-05-05 11:04:53 -06:00
|
|
|
const TEN_MINUTES = 10 * 60 * 1000;
|
|
|
|
|
if (ageMs < TEN_MINUTES) {
|
|
|
|
|
synced++;
|
|
|
|
|
withPlans++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
await pool.query(
|
2026-06-12 13:25:00 -06:00
|
|
|
`INSERT INTO atlas_action_plans_cache (host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, NOW())
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
ON CONFLICT(host_id) DO UPDATE SET
|
2026-05-06 11:44:17 -06:00
|
|
|
has_action_plan = EXCLUDED.has_action_plan,
|
|
|
|
|
plan_count = EXCLUDED.plan_count,
|
|
|
|
|
plans_json = EXCLUDED.plans_json,
|
2026-06-12 13:25:00 -06:00
|
|
|
atlas_known = EXCLUDED.atlas_known,
|
2026-05-06 11:44:17 -06:00
|
|
|
synced_at = EXCLUDED.synced_at`,
|
2026-06-12 13:25:00 -06:00
|
|
|
[hostId, hasActionPlan, planCount, JSON.stringify(allPlans), atlasKnown]
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
);
|
|
|
|
|
} catch (dbErr) {
|
|
|
|
|
console.error('[Atlas Sync] DB upsert failed for host', hostId, ':', dbErr.message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
synced++;
|
|
|
|
|
if (hasActionPlan) withPlans++;
|
|
|
|
|
} else {
|
|
|
|
|
failed++;
|
2026-05-06 11:44:17 -06:00
|
|
|
console.warn(`[Atlas Sync] Non-2xx response for host ${hostId}: status ${result.status}`);
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
logAudit({
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
userId: req.user.id,
|
|
|
|
|
username: req.user.username,
|
|
|
|
|
action: 'ATLAS_SYNC',
|
|
|
|
|
entityType: 'atlas_action_plans',
|
|
|
|
|
entityId: null,
|
|
|
|
|
details: { synced, withPlans, failed, totalHosts: hostIds.length },
|
|
|
|
|
ipAddress: req.ip
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({ synced, withPlans, failed });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas Sync] Unexpected error:', err.message);
|
|
|
|
|
res.status(500).json({ error: 'Atlas sync failed: ' + err.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
/**
|
|
|
|
|
* GET /hosts/:hostId/action-plans
|
|
|
|
|
*
|
|
|
|
|
* Proxies a request to Atlas to retrieve action plans for a specific host.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} req.params.hostId - Positive integer host identifier
|
|
|
|
|
* @returns {Object} 2xx - Action plans response from Atlas API
|
|
|
|
|
* @returns {Object} 400 - { error } when hostId is invalid
|
|
|
|
|
* @returns {Object} 502 - { error } when Atlas API is unreachable
|
|
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
*/
|
2026-05-06 11:44:17 -06:00
|
|
|
router.get('/hosts/:hostId/action-plans', requireAuth(), async (req, res) => {
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hostId = parseInt(req.params.hostId, 10);
|
|
|
|
|
if (!Number.isInteger(hostId) || hostId <= 0) {
|
|
|
|
|
return res.status(400).json({ error: 'hostId must be a positive integer' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await atlasGet('/hosts/' + hostId + '/action-plans');
|
|
|
|
|
if (result.status >= 200 && result.status < 300) {
|
|
|
|
|
let body;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { body = JSON.parse(result.body); } catch (e) { body = result.body; }
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.status(result.status).json(body);
|
|
|
|
|
} else {
|
|
|
|
|
let errorBody;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { errorBody = JSON.parse(result.body); } catch (e) { errorBody = { error: result.body }; }
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.status(result.status).json(errorBody);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas] GET action-plans failed for host', hostId, ':', err.message);
|
|
|
|
|
res.status(502).json({ error: 'Failed to reach Atlas API: ' + err.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
/**
|
|
|
|
|
* PUT /hosts/:hostId/action-plans
|
|
|
|
|
*
|
|
|
|
|
* Creates a new action plan for a host via the Atlas API.
|
|
|
|
|
* Requires Admin or Standard_User group.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} req.params.hostId - Positive integer host identifier
|
|
|
|
|
* @param {Object} req.body
|
|
|
|
|
* @param {string} req.body.plan_type - One of: decommission, remediation, false_positive, risk_acceptance, scan_exclusion
|
|
|
|
|
* @param {string} req.body.commit_date - Date in YYYY-MM-DD format
|
|
|
|
|
* @returns {Object} 2xx - Created plan response from Atlas API
|
|
|
|
|
* @returns {Object} 400 - { error } when hostId, plan_type, or commit_date is invalid
|
|
|
|
|
* @returns {Object} 502 - { error } when Atlas API is unreachable
|
|
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
*/
|
2026-05-06 11:44:17 -06:00
|
|
|
router.put('/hosts/:hostId/action-plans', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hostId = parseInt(req.params.hostId, 10);
|
|
|
|
|
if (!Number.isInteger(hostId) || hostId <= 0) {
|
|
|
|
|
return res.status(400).json({ error: 'hostId must be a positive integer' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { plan_type, commit_date } = req.body || {};
|
|
|
|
|
if (!plan_type || !VALID_PLAN_TYPES.includes(plan_type)) {
|
|
|
|
|
return res.status(400).json({ error: 'plan_type must be one of: ' + VALID_PLAN_TYPES.join(', ') });
|
|
|
|
|
}
|
|
|
|
|
if (!commit_date || !DATE_PATTERN.test(commit_date)) {
|
|
|
|
|
return res.status(400).json({ error: 'commit_date must be a valid YYYY-MM-DD date string' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await atlasPut('/hosts/' + hostId + '/action-plans', req.body);
|
|
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
logAudit({
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
userId: req.user.id,
|
|
|
|
|
username: req.user.username,
|
|
|
|
|
action: 'ATLAS_CREATE_PLAN',
|
|
|
|
|
entityType: 'atlas_action_plan',
|
|
|
|
|
entityId: String(hostId),
|
|
|
|
|
details: { hostId, plan_type, commit_date },
|
|
|
|
|
ipAddress: req.ip
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.status >= 200 && result.status < 300) {
|
|
|
|
|
let body;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { body = JSON.parse(result.body); } catch (e) { body = result.body; }
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.status(result.status).json(body);
|
|
|
|
|
} else {
|
|
|
|
|
let errorBody;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { errorBody = JSON.parse(result.body); } catch (e) { errorBody = { error: result.body }; }
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.status(result.status).json(errorBody);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas] PUT action-plans failed for host', hostId, ':', err.message);
|
|
|
|
|
res.status(502).json({ error: 'Failed to reach Atlas API: ' + err.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
/**
|
|
|
|
|
* PATCH /hosts/:hostId/action-plans
|
|
|
|
|
*
|
|
|
|
|
* Updates an existing action plan for a host via the Atlas API.
|
|
|
|
|
* Requires Admin or Standard_User group.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} req.params.hostId - Positive integer host identifier
|
|
|
|
|
* @param {Object} req.body
|
|
|
|
|
* @param {string} req.body.action_plan_id - Non-empty string identifying the plan to update
|
|
|
|
|
* @param {Object} req.body.updates - Object containing fields to update
|
|
|
|
|
* @returns {Object} 2xx - Updated plan response from Atlas API
|
|
|
|
|
* @returns {Object} 400 - { error } when hostId, action_plan_id, or updates is invalid
|
|
|
|
|
* @returns {Object} 502 - { error } when Atlas API is unreachable
|
|
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
*/
|
2026-05-06 11:44:17 -06:00
|
|
|
router.patch('/hosts/:hostId/action-plans', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hostId = parseInt(req.params.hostId, 10);
|
|
|
|
|
if (!Number.isInteger(hostId) || hostId <= 0) {
|
|
|
|
|
return res.status(400).json({ error: 'hostId must be a positive integer' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { action_plan_id, updates } = req.body || {};
|
|
|
|
|
if (!action_plan_id || typeof action_plan_id !== 'string' || action_plan_id.trim() === '') {
|
|
|
|
|
return res.status(400).json({ error: 'action_plan_id is required and must be a non-empty string' });
|
|
|
|
|
}
|
|
|
|
|
if (!updates || typeof updates !== 'object' || Array.isArray(updates)) {
|
|
|
|
|
return res.status(400).json({ error: 'updates is required and must be an object' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await atlasPatch('/hosts/' + hostId + '/action-plans', req.body);
|
|
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
logAudit({
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
userId: req.user.id,
|
|
|
|
|
username: req.user.username,
|
|
|
|
|
action: 'ATLAS_UPDATE_PLAN',
|
|
|
|
|
entityType: 'atlas_action_plan',
|
|
|
|
|
entityId: String(hostId),
|
|
|
|
|
details: { hostId, action_plan_id },
|
|
|
|
|
ipAddress: req.ip
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.status >= 200 && result.status < 300) {
|
|
|
|
|
let body;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { body = JSON.parse(result.body); } catch (e) { body = result.body; }
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.status(result.status).json(body);
|
|
|
|
|
} else {
|
|
|
|
|
let errorBody;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { errorBody = JSON.parse(result.body); } catch (e) { errorBody = { error: result.body }; }
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.status(result.status).json(errorBody);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas] PATCH action-plans failed for host', hostId, ':', err.message);
|
|
|
|
|
res.status(502).json({ error: 'Failed to reach Atlas API: ' + err.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
/**
|
|
|
|
|
* POST /hosts/bulk-action-plans
|
|
|
|
|
*
|
|
|
|
|
* Creates action plans for multiple hosts in a single request via the Atlas API.
|
|
|
|
|
* Optimistically updates the local cache with stub plans after a successful response.
|
|
|
|
|
* Requires Admin or Standard_User group.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} req.body
|
|
|
|
|
* @param {number[]} req.body.host_ids - Non-empty array of positive integer host identifiers
|
|
|
|
|
* @param {string} req.body.plan_type - One of: decommission, remediation, false_positive, risk_acceptance, scan_exclusion
|
|
|
|
|
* @param {string} req.body.commit_date - Date in YYYY-MM-DD format
|
|
|
|
|
* @returns {Object} 2xx - Bulk creation response from Atlas API
|
|
|
|
|
* @returns {Object} 400 - { error } when host_ids, plan_type, or commit_date is invalid
|
|
|
|
|
* @returns {Object} 502 - { error } when Atlas API is unreachable
|
|
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
*/
|
2026-05-06 11:44:17 -06:00
|
|
|
router.post('/hosts/bulk-action-plans', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { host_ids, plan_type, commit_date } = req.body || {};
|
|
|
|
|
if (!Array.isArray(host_ids) || host_ids.length === 0) {
|
|
|
|
|
return res.status(400).json({ error: 'host_ids must be a non-empty array of positive integers' });
|
|
|
|
|
}
|
|
|
|
|
for (const id of host_ids) {
|
|
|
|
|
if (!Number.isInteger(id) || id <= 0) {
|
|
|
|
|
return res.status(400).json({ error: 'host_ids must be a non-empty array of positive integers' });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!plan_type || !VALID_PLAN_TYPES.includes(plan_type)) {
|
|
|
|
|
return res.status(400).json({ error: 'plan_type must be one of: ' + VALID_PLAN_TYPES.join(', ') });
|
|
|
|
|
}
|
|
|
|
|
if (!commit_date || !DATE_PATTERN.test(commit_date)) {
|
|
|
|
|
return res.status(400).json({ error: 'commit_date must be a valid YYYY-MM-DD date string' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await atlasPost('/hosts/create-bulk-action-plans', req.body);
|
|
|
|
|
|
|
|
|
|
if (result.status >= 200 && result.status < 300) {
|
|
|
|
|
let body;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { body = JSON.parse(result.body); } catch (e) { body = result.body; }
|
2026-05-05 11:04:53 -06:00
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
// Optimistically update local cache
|
2026-05-05 11:04:53 -06:00
|
|
|
for (const hid of host_ids) {
|
|
|
|
|
try {
|
2026-05-06 11:44:17 -06:00
|
|
|
const { rows: existingRows } = await pool.query(
|
|
|
|
|
`SELECT plan_count, plans_json FROM atlas_action_plans_cache WHERE host_id = $1`,
|
2026-05-05 11:04:53 -06:00
|
|
|
[hid]
|
|
|
|
|
);
|
2026-05-06 11:44:17 -06:00
|
|
|
const existing = existingRows[0];
|
2026-05-05 11:04:53 -06:00
|
|
|
|
|
|
|
|
let existingPlans = [];
|
|
|
|
|
if (existing && existing.plans_json) {
|
2026-05-06 11:44:17 -06:00
|
|
|
try { existingPlans = JSON.parse(existing.plans_json); } catch (_) {}
|
2026-05-05 11:04:53 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const stubPlan = { plan_type, commit_date, source: 'bulk-create', created_at: new Date().toISOString() };
|
|
|
|
|
const updatedPlans = [...existingPlans, stubPlan];
|
|
|
|
|
const newCount = updatedPlans.length;
|
|
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
await pool.query(
|
2026-06-12 13:25:00 -06:00
|
|
|
`INSERT INTO atlas_action_plans_cache (host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at)
|
|
|
|
|
VALUES ($1, true, $2, $3, true, NOW())
|
2026-05-05 11:04:53 -06:00
|
|
|
ON CONFLICT(host_id) DO UPDATE SET
|
2026-05-06 11:44:17 -06:00
|
|
|
has_action_plan = true,
|
|
|
|
|
plan_count = EXCLUDED.plan_count,
|
|
|
|
|
plans_json = EXCLUDED.plans_json,
|
2026-06-12 13:25:00 -06:00
|
|
|
atlas_known = true,
|
2026-05-06 11:44:17 -06:00
|
|
|
synced_at = EXCLUDED.synced_at`,
|
2026-05-05 11:04:53 -06:00
|
|
|
[hid, newCount, JSON.stringify(updatedPlans)]
|
|
|
|
|
);
|
|
|
|
|
} catch (cacheErr) {
|
|
|
|
|
console.error('[Atlas] Cache update failed for host', hid, ':', cacheErr.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 11:44:17 -06:00
|
|
|
logAudit({
|
2026-05-05 11:04:53 -06:00
|
|
|
userId: req.user.id,
|
|
|
|
|
username: req.user.username,
|
|
|
|
|
action: 'ATLAS_BULK_CREATE_PLANS',
|
|
|
|
|
entityType: 'atlas_action_plan',
|
|
|
|
|
entityId: null,
|
|
|
|
|
details: { host_ids, plan_type, commit_date, count: host_ids.length },
|
|
|
|
|
ipAddress: req.ip
|
|
|
|
|
});
|
|
|
|
|
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.status(result.status).json(body);
|
|
|
|
|
} else {
|
|
|
|
|
let errorBody;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { errorBody = JSON.parse(result.body); } catch (e) { errorBody = { error: result.body }; }
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
res.status(result.status).json(errorBody);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas] POST bulk-action-plans failed:', err.message);
|
|
|
|
|
res.status(502).json({ error: 'Failed to reach Atlas API: ' + err.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
/**
|
|
|
|
|
* POST /hosts/:hostId/refresh-cache
|
|
|
|
|
*
|
|
|
|
|
* Triggers Atlas to refresh its Ivanti data cache, then updates the local
|
|
|
|
|
* action plans cache for the specified host. Useful when action plan creation
|
|
|
|
|
* fails due to stale finding IDs.
|
|
|
|
|
* Requires Admin or Standard_User group.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} req.params.hostId - Positive integer host identifier
|
|
|
|
|
* @returns {Object} 200 - { success, message } on successful cache refresh
|
|
|
|
|
* @returns {Object} 400 - { error } when hostId is invalid
|
|
|
|
|
* @returns {Object} 502 - { error } when Atlas API is unreachable
|
|
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
*/
|
|
|
|
|
router.post('/hosts/:hostId/refresh-cache', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
|
|
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hostId = parseInt(req.params.hostId, 10);
|
|
|
|
|
if (!Number.isInteger(hostId) || hostId <= 0) {
|
|
|
|
|
return res.status(400).json({ error: 'hostId must be a positive integer' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await atlasPost('/cache/refresh-ivanti', {}, { timeout: 30000 });
|
|
|
|
|
|
|
|
|
|
if (result.status >= 200 && result.status < 300) {
|
|
|
|
|
// Also refresh our local action plans cache for this host
|
|
|
|
|
const plansResult = await atlasGet('/hosts/' + hostId + '/action-plans');
|
|
|
|
|
if (plansResult.status >= 200 && plansResult.status < 300) {
|
|
|
|
|
let allPlans = [];
|
|
|
|
|
let activePlans = [];
|
|
|
|
|
try {
|
|
|
|
|
const parsed = JSON.parse(plansResult.body);
|
|
|
|
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
|
|
|
activePlans = Array.isArray(parsed.active) ? parsed.active : [];
|
|
|
|
|
const inactive = Array.isArray(parsed.inactive) ? parsed.inactive : [];
|
|
|
|
|
allPlans = [...activePlans, ...inactive];
|
|
|
|
|
} else if (Array.isArray(parsed)) {
|
|
|
|
|
allPlans = parsed;
|
|
|
|
|
activePlans = parsed;
|
|
|
|
|
}
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
|
|
const planCount = activePlans.length;
|
|
|
|
|
const hasActionPlan = planCount > 0;
|
2026-06-12 13:25:00 -06:00
|
|
|
const atlasKnown = allPlans.length > 0;
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
|
|
|
|
|
await pool.query(
|
2026-06-12 13:25:00 -06:00
|
|
|
`INSERT INTO atlas_action_plans_cache (host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, NOW())
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
ON CONFLICT(host_id) DO UPDATE SET
|
|
|
|
|
has_action_plan = EXCLUDED.has_action_plan,
|
|
|
|
|
plan_count = EXCLUDED.plan_count,
|
|
|
|
|
plans_json = EXCLUDED.plans_json,
|
2026-06-12 13:25:00 -06:00
|
|
|
atlas_known = EXCLUDED.atlas_known,
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
synced_at = EXCLUDED.synced_at`,
|
2026-06-12 13:25:00 -06:00
|
|
|
[hostId, hasActionPlan, planCount, JSON.stringify(allPlans), atlasKnown]
|
Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
2026-05-08 12:47:39 -06:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({ success: true, message: 'Atlas cache refreshed for host ' + hostId });
|
|
|
|
|
} else {
|
|
|
|
|
let errorBody;
|
|
|
|
|
try { errorBody = JSON.parse(result.body); } catch (e) { errorBody = { error: result.body }; }
|
|
|
|
|
res.status(result.status).json(errorBody);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas] POST refresh-cache failed for host', hostId, ':', err.message);
|
|
|
|
|
res.status(502).json({ error: 'Failed to reach Atlas API: ' + err.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /hosts/vulnerabilities
|
|
|
|
|
*
|
|
|
|
|
* Fetches Ivanti vulnerability data for the specified hosts from Atlas.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} req.body
|
|
|
|
|
* @param {number[]} req.body.host_ids - Non-empty array of positive integer host identifiers
|
|
|
|
|
* @returns {Object} 2xx - Vulnerability data response from Atlas API
|
|
|
|
|
* @returns {Object} 400 - { error } when host_ids is invalid
|
|
|
|
|
* @returns {Object} 502 - { error } when Atlas API is unreachable
|
|
|
|
|
* @returns {Object} 503 - { error } when Atlas API is not configured
|
|
|
|
|
*/
|
2026-05-06 11:44:17 -06:00
|
|
|
router.post('/hosts/vulnerabilities', requireAuth(), async (req, res) => {
|
2026-04-24 22:07:55 +00:00
|
|
|
if (!isConfigured) {
|
|
|
|
|
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { host_ids } = req.body || {};
|
|
|
|
|
if (!Array.isArray(host_ids) || host_ids.length === 0) {
|
|
|
|
|
return res.status(400).json({ error: 'host_ids must be a non-empty array of positive integers' });
|
|
|
|
|
}
|
|
|
|
|
for (const id of host_ids) {
|
|
|
|
|
if (!Number.isInteger(id) || id <= 0) {
|
|
|
|
|
return res.status(400).json({ error: 'host_ids must be a non-empty array of positive integers' });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await atlasPost('/ivanti-vulnerabilities-by-host', { host_ids }, { timeout: 30000 });
|
|
|
|
|
|
|
|
|
|
if (result.status >= 200 && result.status < 300) {
|
|
|
|
|
let body;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { body = JSON.parse(result.body); } catch (e) { body = result.body; }
|
2026-04-24 22:07:55 +00:00
|
|
|
res.status(result.status).json(body);
|
|
|
|
|
} else {
|
|
|
|
|
let errorBody;
|
2026-05-06 11:44:17 -06:00
|
|
|
try { errorBody = JSON.parse(result.body); } catch (e) { errorBody = { error: result.body }; }
|
2026-04-24 22:07:55 +00:00
|
|
|
res.status(result.status).json(errorBody);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[Atlas] POST hosts/vulnerabilities failed:', err.message);
|
|
|
|
|
res.status(502).json({ error: 'Failed to reach Atlas API: ' + err.message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00
|
|
|
return router;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = createAtlasRouter;
|
2026-04-24 17:30:06 +00:00
|
|
|
module.exports.aggregateAtlasMetrics = aggregateAtlasMetrics;
|