feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison) - Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis - Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison) - Add veeam-analysis-state.ts and rmm-device-resolver.ts services - Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis - Add backup-status page updates and nav links for new Veeam pages - Add scripts: deactivate-cis-for-inactive-companies, workstation category updates - Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt - Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
This commit is contained in:
parent
07067bef19
commit
ea3471d38d
36 changed files with 5604 additions and 217 deletions
42
migrations/065_create_company_teams_table.sql
Normal file
42
migrations/065_create_company_teams_table.sql
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
-- Migration 065: Create company_teams table and add primary/billing contact FKs to companies
|
||||
|
||||
-- Company Teams: maps resources (TAMs, CSMs, etc.) to companies
|
||||
CREATE TABLE IF NOT EXISTS company_teams (
|
||||
id BIGINT PRIMARY KEY,
|
||||
company_id BIGINT REFERENCES companies(id) ON DELETE CASCADE,
|
||||
resource_id BIGINT,
|
||||
is_associated_as_comanaged BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_deleted BOOLEAN DEFAULT false,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_company_teams_company_id ON company_teams(company_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_company_teams_resource_id ON company_teams(resource_id);
|
||||
|
||||
-- Add primary and billing contact FK columns to companies
|
||||
ALTER TABLE companies
|
||||
ADD COLUMN IF NOT EXISTS primary_contact_id BIGINT REFERENCES contacts(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS billing_contact_id BIGINT REFERENCES contacts(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_companies_primary_contact ON companies(primary_contact_id) WHERE primary_contact_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_companies_billing_contact ON companies(billing_contact_id) WHERE billing_contact_id IS NOT NULL;
|
||||
|
||||
-- Backfill from existing contacts data
|
||||
UPDATE companies c
|
||||
SET primary_contact_id = (
|
||||
SELECT id FROM contacts
|
||||
WHERE company_id = c.id AND primary_contact = true AND is_deleted = false
|
||||
ORDER BY id LIMIT 1
|
||||
)
|
||||
WHERE primary_contact_id IS NULL;
|
||||
|
||||
UPDATE companies c
|
||||
SET billing_contact_id = (
|
||||
SELECT id FROM contacts
|
||||
WHERE company_id = c.id AND billing_contact = true AND is_deleted = false
|
||||
ORDER BY id LIMIT 1
|
||||
)
|
||||
WHERE billing_contact_id IS NULL;
|
||||
21
migrations/066_create_veeam_rpo_offline_log.sql
Normal file
21
migrations/066_create_veeam_rpo_offline_log.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
-- Tracks every RPO check cycle where a workstation job was suppressed
|
||||
-- because the device was offline (last_seen older than its backup interval).
|
||||
-- Used for auditing, reporting, and the Claude Code skill.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS veeam_rpo_offline_log (
|
||||
id SERIAL PRIMARY KEY,
|
||||
job_instance_uid VARCHAR(255) NOT NULL,
|
||||
job_name TEXT NOT NULL,
|
||||
org_name TEXT NOT NULL,
|
||||
rmm_hostname TEXT NOT NULL,
|
||||
rmm_site_name TEXT NOT NULL,
|
||||
device_type_category TEXT NOT NULL,
|
||||
rmm_last_seen TIMESTAMPTZ,
|
||||
hours_offline NUMERIC(10,2) NOT NULL,
|
||||
backup_interval_hours INTEGER NOT NULL,
|
||||
checked_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_offline_log_job ON veeam_rpo_offline_log(job_instance_uid);
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_offline_log_checked_at ON veeam_rpo_offline_log(checked_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_offline_log_hostname ON veeam_rpo_offline_log(rmm_hostname);
|
||||
73
migrations/067_create_veeam_rpo_comparison_tables.sql
Normal file
73
migrations/067_create_veeam_rpo_comparison_tables.sql
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
-- Veeam RPO shadow-mode tables for the parallel validation period.
|
||||
-- The RPO service runs without creating real Autotask tickets; all actions
|
||||
-- are logged here so they can be compared against Datto RMM ticket output.
|
||||
|
||||
-- Shadow log: one row per check cycle per job for every action the live service
|
||||
-- *would* have taken (would_create, would_escalate, would_resolve, would_skip_too_old).
|
||||
-- Offline suppressions continue to go to veeam_rpo_offline_log (migration 066).
|
||||
CREATE TABLE IF NOT EXISTS veeam_rpo_shadow_log (
|
||||
id SERIAL PRIMARY KEY,
|
||||
job_instance_uid VARCHAR(255) NOT NULL,
|
||||
job_name TEXT NOT NULL,
|
||||
org_name TEXT NOT NULL,
|
||||
rmm_hostname TEXT,
|
||||
rmm_site_name TEXT,
|
||||
action TEXT NOT NULL,
|
||||
-- 'would_create' | 'would_escalate' | 'would_resolve' | 'would_skip_too_old'
|
||||
priority_level TEXT, -- medium | high | critical (null for would_resolve)
|
||||
hours_overdue INTEGER, -- null for would_resolve
|
||||
failure_category TEXT,
|
||||
checked_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_shadow_log_job ON veeam_rpo_shadow_log(job_instance_uid);
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_shadow_log_checked ON veeam_rpo_shadow_log(checked_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_shadow_log_hostname ON veeam_rpo_shadow_log(rmm_hostname);
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_shadow_log_action ON veeam_rpo_shadow_log(action);
|
||||
|
||||
-- Shadow ticket state: mirrors veeam_rpo_tickets but without Autotask ticket IDs.
|
||||
-- Tracks open/resolved state so the shadow service can distinguish would_create
|
||||
-- from would_escalate across check cycles.
|
||||
CREATE TABLE IF NOT EXISTS veeam_rpo_shadow_tickets (
|
||||
job_instance_uid VARCHAR(255) PRIMARY KEY,
|
||||
job_name TEXT NOT NULL,
|
||||
org_name TEXT NOT NULL,
|
||||
rmm_hostname TEXT,
|
||||
priority_level TEXT NOT NULL,
|
||||
hours_overdue INTEGER NOT NULL,
|
||||
failure_category TEXT,
|
||||
opened_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
resolved_at TIMESTAMPTZ,
|
||||
last_checked_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_shadow_tickets_open ON veeam_rpo_shadow_tickets(resolved_at) WHERE resolved_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_shadow_tickets_hostname ON veeam_rpo_shadow_tickets(rmm_hostname);
|
||||
|
||||
-- Datto RMM backup tickets captured from Autotask webhooks.
|
||||
-- Populated whenever a ticket.created event arrives that matches the Datto
|
||||
-- backup monitor signature (issue_type=38, Veeam-related title, not [Veeam RPO]).
|
||||
-- Updated on ticket.updated events to track resolution.
|
||||
CREATE TABLE IF NOT EXISTS veeam_rpo_datto_tickets (
|
||||
id SERIAL PRIMARY KEY,
|
||||
at_ticket_id BIGINT UNIQUE NOT NULL,
|
||||
at_ticket_number TEXT,
|
||||
title TEXT NOT NULL,
|
||||
company_id BIGINT,
|
||||
company_name TEXT,
|
||||
extracted_hostname TEXT, -- parsed from title (e.g. "DT061" from "DT061 - Veeam...")
|
||||
issue_type INTEGER,
|
||||
sub_issue_type INTEGER,
|
||||
status INTEGER,
|
||||
priority INTEGER,
|
||||
ticket_created_at TIMESTAMPTZ,
|
||||
ticket_resolved_at TIMESTAMPTZ,
|
||||
captured_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_datto_tickets_company ON veeam_rpo_datto_tickets(company_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_datto_tickets_hostname ON veeam_rpo_datto_tickets(extracted_hostname);
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_datto_tickets_created ON veeam_rpo_datto_tickets(ticket_created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_veeam_rpo_datto_tickets_resolved ON veeam_rpo_datto_tickets(ticket_resolved_at) WHERE ticket_resolved_at IS NULL;
|
||||
31
migrations/068_create_veeam_ticket_analysis.sql
Normal file
31
migrations/068_create_veeam_ticket_analysis.sql
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
CREATE TABLE IF NOT EXISTS veeam_ticket_analysis (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticket_number TEXT NOT NULL UNIQUE,
|
||||
ticket_id BIGINT,
|
||||
company_id BIGINT,
|
||||
company_name TEXT,
|
||||
device_hostname TEXT,
|
||||
ticket_created_at TIMESTAMPTZ,
|
||||
ticket_closed_at TIMESTAMPTZ,
|
||||
same_day_close BOOLEAN NOT NULL DEFAULT false,
|
||||
hours_worked NUMERIC(10,2) NOT NULL DEFAULT 0,
|
||||
note_count INTEGER NOT NULL DEFAULT 0,
|
||||
-- LLM classification
|
||||
problem_category TEXT NOT NULL,
|
||||
resolution_type TEXT NOT NULL,
|
||||
skills_required TEXT[] NOT NULL DEFAULT '{}',
|
||||
complexity TEXT NOT NULL,
|
||||
device_was_offline BOOLEAN,
|
||||
backup_completed_before_tech BOOLEAN,
|
||||
preventable BOOLEAN,
|
||||
work_summary TEXT,
|
||||
recommended_procedure TEXT,
|
||||
-- meta
|
||||
model TEXT,
|
||||
analyzed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_vta_ticket_number ON veeam_ticket_analysis(ticket_number);
|
||||
CREATE INDEX IF NOT EXISTS idx_vta_category ON veeam_ticket_analysis(problem_category);
|
||||
CREATE INDEX IF NOT EXISTS idx_vta_company ON veeam_ticket_analysis(company_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_vta_created ON veeam_ticket_analysis(ticket_created_at);
|
||||
Loading…
Add table
Add a link
Reference in a new issue