-- AppGate SDP integration — Postgres schema. -- -- Mirrors the slices of Appgate SDP Controller REST API v22.5 that Pulse -- surfaces for the manager-on-the-go view: -- -- • Active sessions (current snapshot) -> appgate_active_sessions -- • On-boarded devices (slowly changing list) -> appgate_devices -- • Controllers / Gateways -> appgate_appliances -- • Per-user license consumption -> appgate_license_users -- • Daily license capacity rollup -> appgate_license_snapshots -- • Daily login totals (history series) -> appgate_user_logins_daily -- Current active sessions — replaced each sync (no history beyond appliances log). CREATE TABLE IF NOT EXISTS appgate_active_sessions ( distinguished_name TEXT PRIMARY KEY, device_id UUID, username TEXT, provider_name TEXT, hostname TEXT, os_family TEXT, os_name TEXT, os_parent TEXT, client_version TEXT, client_type TEXT, client_support TEXT, geo_ip_latitude DOUBLE PRECISION, geo_ip_longitude DOUBLE PRECISION, gateways JSONB, synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_appgate_sessions_username ON appgate_active_sessions(username); CREATE INDEX IF NOT EXISTS idx_appgate_sessions_provider ON appgate_active_sessions(provider_name); -- On-boarded devices — persistent inventory. Uses the same soft-delete -- pattern as qbo_invoices: a full sync tombstones any row not returned. CREATE TABLE IF NOT EXISTS appgate_devices ( distinguished_name TEXT PRIMARY KEY, device_id UUID, username TEXT, provider_name TEXT, device_type TEXT, hostname TEXT, on_boarded_at TIMESTAMPTZ, last_seen_at TIMESTAMPTZ, synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), is_deleted BOOLEAN NOT NULL DEFAULT false, deleted_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_appgate_devices_username ON appgate_devices(username); CREATE INDEX IF NOT EXISTS idx_appgate_devices_is_deleted ON appgate_devices(is_deleted); CREATE INDEX IF NOT EXISTS idx_appgate_devices_last_seen ON appgate_devices(last_seen_at); -- Appliances (controllers, gateways, log servers, etc.). CREATE TABLE IF NOT EXISTS appgate_appliances ( id UUID PRIMARY KEY, name TEXT NOT NULL, hostname TEXT, notes TEXT, version INTEGER, site TEXT, site_name TEXT, activated BOOLEAN, pending_certificate_renewal BOOLEAN, tags JSONB, roles JSONB, -- {controller, gateway, logServer, ...} subset flags raw JSONB, -- full payload — versioned schema changes created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ, synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), is_deleted BOOLEAN NOT NULL DEFAULT false, deleted_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_appgate_appliances_is_deleted ON appgate_appliances(is_deleted); CREATE INDEX IF NOT EXISTS idx_appgate_appliances_site ON appgate_appliances(site); -- Per-user license consumption — current snapshot, replaced each sync. CREATE TABLE IF NOT EXISTS appgate_license_users ( user_distinguished_name TEXT PRIMARY KEY, username TEXT, provider_name TEXT, license_type TEXT, profile_name TEXT, created_at TIMESTAMPTZ, last_seen_at TIMESTAMPTZ, synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_appgate_license_users_username ON appgate_license_users(username); -- Daily license capacity rollup — one row per day, history retained. CREATE TABLE IF NOT EXISTS appgate_license_snapshots ( snapshot_date DATE PRIMARY KEY, max_users INTEGER, max_portal_users INTEGER, max_service_users INTEGER, used_users INTEGER, expiration TIMESTAMPTZ, license_type INTEGER, license_version INTEGER, risk_engine_enabled BOOLEAN, app_discovery_enabled BOOLEAN, captured_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Daily login totals — derived from the rolling 24h /stats/user-logins endpoint. -- Sync replaces yesterday/today rows; older days never change. CREATE TABLE IF NOT EXISTS appgate_user_logins_daily ( login_date DATE PRIMARY KEY, total_logins INTEGER NOT NULL DEFAULT 0, captured_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Sync history. CREATE TABLE IF NOT EXISTS appgate_sync_history ( id SERIAL PRIMARY KEY, sync_id TEXT NOT NULL, sync_type TEXT NOT NULL, triggered_by TEXT NOT NULL, started_at TIMESTAMPTZ NOT NULL, completed_at TIMESTAMPTZ, status TEXT NOT NULL, sessions_synced INTEGER NOT NULL DEFAULT 0, devices_synced INTEGER NOT NULL DEFAULT 0, devices_tombstoned INTEGER NOT NULL DEFAULT 0, appliances_synced INTEGER NOT NULL DEFAULT 0, appliances_tombstoned INTEGER NOT NULL DEFAULT 0, license_users_synced INTEGER NOT NULL DEFAULT 0, last_error TEXT ); CREATE INDEX IF NOT EXISTS idx_appgate_sync_history_started ON appgate_sync_history(started_at DESC); -- Scheduler entries — disabled by default until credentials configured. -- sync_schedules has no unique constraint on name, so guard with NOT EXISTS. INSERT INTO sync_schedules (id, name, description, cron_expression, sync_type, is_enabled) SELECT 'appgate-sessions', 'AppGate Sessions', 'Active session snapshot every 5 minutes during business hours.', '*/5 11-23 * * 1-5', 'appgate-sessions', false WHERE NOT EXISTS (SELECT 1 FROM sync_schedules WHERE name = 'AppGate Sessions'); INSERT INTO sync_schedules (id, name, description, cron_expression, sync_type, is_enabled) SELECT 'appgate-daily', 'AppGate Daily', 'Full AppGate sync — devices, appliances, license, login totals.', '15 6 * * *', 'appgate-daily', false WHERE NOT EXISTS (SELECT 1 FROM sync_schedules WHERE name = 'AppGate Daily');