Add atlas_known distinction to prevent badge noise for untracked hosts
Atlas sync now distinguishes between hosts Atlas actively tracks (returned plans, active or inactive) vs hosts with empty responses (not in Atlas). Only atlas_known hosts show the badge — ACCESS-OPS hosts not covered by Atlas won't show the amber '0' warning badge anymore. Changes: - Migration adds atlas_known BOOLEAN column to atlas_action_plans_cache - Sync sets atlas_known = true only when Atlas returns at least one plan - Metrics endpoint only counts atlas_known hosts in its aggregation - Status endpoint includes atlas_known in response - AtlasBadge renders nothing when atlas_known = false - Bulk-create and refresh-cache upserts set atlas_known = true - Backfill marks existing hosts with plans + managed BU hosts as known
This commit is contained in:
@@ -101,19 +101,20 @@ function createAtlasRouter() {
|
||||
INNER JOIN (
|
||||
SELECT DISTINCT host_id FROM ivanti_findings
|
||||
WHERE bu_ownership ILIKE ANY($1::text[])
|
||||
) f ON a.host_id = f.host_id`,
|
||||
) f ON a.host_id = f.host_id
|
||||
WHERE a.atlas_known = true`,
|
||||
[patterns]
|
||||
);
|
||||
rows = result.rows;
|
||||
} else {
|
||||
const result = await pool.query(
|
||||
`SELECT has_action_plan, plans_json FROM atlas_action_plans_cache`
|
||||
`SELECT has_action_plan, plans_json FROM atlas_action_plans_cache WHERE atlas_known = true`
|
||||
);
|
||||
rows = result.rows;
|
||||
}
|
||||
} else {
|
||||
const result = await pool.query(
|
||||
`SELECT has_action_plan, plans_json FROM atlas_action_plans_cache`
|
||||
`SELECT has_action_plan, plans_json FROM atlas_action_plans_cache WHERE atlas_known = true`
|
||||
);
|
||||
rows = result.rows;
|
||||
}
|
||||
@@ -134,7 +135,7 @@ function createAtlasRouter() {
|
||||
* belonging to specific BUs (via JOIN on ivanti_findings).
|
||||
*
|
||||
* @query {string} [teams] - Comma-separated team names (e.g. 'STEAM,ACCESS-ENG')
|
||||
* @returns {Array} 200 - Array of { host_id, has_action_plan, plan_count, plans_json, synced_at }
|
||||
* @returns {Array} 200 - Array of { host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at }
|
||||
* @returns {Object} 503 - { error } when Atlas API is not configured
|
||||
* @returns {Object} 500 - { error } on database failure
|
||||
*/
|
||||
@@ -152,7 +153,7 @@ function createAtlasRouter() {
|
||||
if (teams.length > 0) {
|
||||
const patterns = teams.map(t => `%${t}%`);
|
||||
const result = await pool.query(
|
||||
`SELECT a.host_id, a.has_action_plan, a.plan_count, a.plans_json, a.synced_at
|
||||
`SELECT a.host_id, a.has_action_plan, a.plan_count, a.plans_json, a.atlas_known, a.synced_at
|
||||
FROM atlas_action_plans_cache a
|
||||
INNER JOIN (
|
||||
SELECT DISTINCT host_id FROM ivanti_findings
|
||||
@@ -163,13 +164,13 @@ function createAtlasRouter() {
|
||||
rows = result.rows;
|
||||
} else {
|
||||
const result = await pool.query(
|
||||
`SELECT host_id, has_action_plan, plan_count, plans_json, synced_at FROM atlas_action_plans_cache`
|
||||
`SELECT host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at FROM atlas_action_plans_cache`
|
||||
);
|
||||
rows = result.rows;
|
||||
}
|
||||
} else {
|
||||
const result = await pool.query(
|
||||
`SELECT host_id, has_action_plan, plan_count, plans_json, synced_at FROM atlas_action_plans_cache`
|
||||
`SELECT host_id, has_action_plan, plan_count, plans_json, atlas_known, synced_at FROM atlas_action_plans_cache`
|
||||
);
|
||||
rows = result.rows;
|
||||
}
|
||||
@@ -186,8 +187,12 @@ function createAtlasRouter() {
|
||||
*
|
||||
* 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.
|
||||
* Scopes to the provided teams or falls back to IVANTI_MANAGED_BUS.
|
||||
* Requires Admin or Standard_User group.
|
||||
*
|
||||
* @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)
|
||||
* @returns {Object} 200 - { synced, withPlans, failed }
|
||||
* @returns {Object} 503 - { error } when Atlas API is not configured
|
||||
* @returns {Object} 500 - { error } on unexpected failure
|
||||
@@ -289,6 +294,9 @@ function createAtlasRouter() {
|
||||
|
||||
const planCount = activePlans.length;
|
||||
const hasActionPlan = planCount > 0;
|
||||
// Atlas "knows" this host if it returned any plans (active or inactive).
|
||||
// Hosts with completely empty responses are not tracked by Atlas.
|
||||
const atlasKnown = allPlans.length > 0;
|
||||
|
||||
try {
|
||||
if (!hasActionPlan) {
|
||||
@@ -314,14 +322,15 @@ function createAtlasRouter() {
|
||||
}
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO atlas_action_plans_cache (host_id, has_action_plan, plan_count, plans_json, synced_at)
|
||||
VALUES ($1, $2, $3, $4, NOW())
|
||||
`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())
|
||||
ON CONFLICT(host_id) DO UPDATE SET
|
||||
has_action_plan = EXCLUDED.has_action_plan,
|
||||
plan_count = EXCLUDED.plan_count,
|
||||
plans_json = EXCLUDED.plans_json,
|
||||
atlas_known = EXCLUDED.atlas_known,
|
||||
synced_at = EXCLUDED.synced_at`,
|
||||
[hostId, hasActionPlan, planCount, JSON.stringify(allPlans)]
|
||||
[hostId, hasActionPlan, planCount, JSON.stringify(allPlans), atlasKnown]
|
||||
);
|
||||
} catch (dbErr) {
|
||||
console.error('[Atlas Sync] DB upsert failed for host', hostId, ':', dbErr.message);
|
||||
@@ -576,12 +585,13 @@ function createAtlasRouter() {
|
||||
const newCount = updatedPlans.length;
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO atlas_action_plans_cache (host_id, has_action_plan, plan_count, plans_json, synced_at)
|
||||
VALUES ($1, true, $2, $3, NOW())
|
||||
`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())
|
||||
ON CONFLICT(host_id) DO UPDATE SET
|
||||
has_action_plan = true,
|
||||
plan_count = EXCLUDED.plan_count,
|
||||
plans_json = EXCLUDED.plans_json,
|
||||
atlas_known = true,
|
||||
synced_at = EXCLUDED.synced_at`,
|
||||
[hid, newCount, JSON.stringify(updatedPlans)]
|
||||
);
|
||||
@@ -659,16 +669,18 @@ function createAtlasRouter() {
|
||||
|
||||
const planCount = activePlans.length;
|
||||
const hasActionPlan = planCount > 0;
|
||||
const atlasKnown = allPlans.length > 0;
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO atlas_action_plans_cache (host_id, has_action_plan, plan_count, plans_json, synced_at)
|
||||
VALUES ($1, $2, $3, $4, NOW())
|
||||
`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())
|
||||
ON CONFLICT(host_id) DO UPDATE SET
|
||||
has_action_plan = EXCLUDED.has_action_plan,
|
||||
plan_count = EXCLUDED.plan_count,
|
||||
plans_json = EXCLUDED.plans_json,
|
||||
atlas_known = EXCLUDED.atlas_known,
|
||||
synced_at = EXCLUDED.synced_at`,
|
||||
[hostId, hasActionPlan, planCount, JSON.stringify(allPlans)]
|
||||
[hostId, hasActionPlan, planCount, JSON.stringify(allPlans), atlasKnown]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user