feat(appgate): add AppGate SDP integration health check and sync service

Registers AppGate as a checkConfigOnly integration-health row and public
sync route, matching the existing factory + is<Name>Configured() pattern.
Committed now so Phase 13's worktree-isolated executors fork from a HEAD
that includes this integration-health.ts entry, since Plan 13-02 inserts
the PAX8 row immediately after it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
This commit is contained in:
lorentz 2026-07-11 09:43:48 -04:00
parent f70a4a36ed
commit b168d44585
9 changed files with 998 additions and 0 deletions

130
lib/types/appgate.ts Normal file
View file

@ -0,0 +1,130 @@
/**
* Type definitions for the AppGate SDP Controller REST API (v22.5) and the
* shapes Pulse persists. Source spec lives at
* `https://wawnvaagp01.wulfconsulting.com:8443/api_specs.html`.
*
* Only the slices Pulse consumes are typed the API exposes ~150 endpoints,
* most of which are administrative writes Pulse never makes.
*/
// ─── API response shapes ──────────────────────────────────────────────────
export interface AppgateLoginResponse {
user?: unknown;
token: string;
expires: string; // ISO timestamp
messageOfTheDay?: string;
ztpCollectiveType?: string;
ztpAccountType?: string;
crlEnabled?: boolean;
}
export interface AppgateDeviceAndUser {
distinguishedName: string;
deviceId?: string;
username?: string;
providerName?: string;
}
export interface AppgateActiveSession extends AppgateDeviceAndUser {
geoIpLatitude?: number;
geoIpLongitude?: number;
hostname?: string;
osFamily?: string;
osName?: string;
osParent?: string;
clientVersion?: string;
clientType?: string;
clientSupport?: 'full' | 'partial' | 'none';
gateways?: string[];
}
export interface AppgateOnBoardedDevice extends AppgateDeviceAndUser {
device_type?: string;
hostname?: string;
onBoardedAt?: string;
lastSeenAt?: string;
}
export interface AppgateAppliance {
id: string;
name: string;
notes?: string;
hostname?: string;
version?: number;
site?: string;
siteName?: string;
activated?: boolean;
pendingCertificateRenewal?: boolean;
tags?: string[];
created?: string;
updated?: string;
controller?: { enabled?: boolean };
gateway?: { enabled?: boolean };
logServer?: { enabled?: boolean };
logForwarder?: { enabled?: boolean };
metricsAggregator?: { enabled?: boolean };
connector?: { enabled?: boolean };
portal?: { enabled?: boolean };
[key: string]: unknown;
}
export interface AppgateUserLicense {
userDistinguishedName: string;
username?: string;
providerName?: string;
type?: string; // user | portal | service
profileName?: string;
created?: string;
lastSeenAt?: string;
}
export interface AppgateLicense {
id?: string;
version?: number;
type?: number; // 1 production, 2 install, 3 test, 4 built-in, 5 aws, 6 metered
expiration?: string;
error?: string;
maxUsers?: number;
maxPortalUsers?: number;
maxServiceUsers?: number;
maxSites?: number;
maxAccessPolicies?: number;
maxConnectorGroups?: number;
riskEngine?: boolean;
applicationDiscovery?: boolean;
digitalExperienceMonitoring?: boolean;
}
/**
* `/stats/user-logins` returns a 24-hour rolling map keyed by hour-of-day,
* each value either a number or `{ total: number }` per-controller breakdown.
* Hour keys past "now" represent yesterday; everything else is today.
*/
export type AppgateHourlyLogins = Record<
string,
number | { total?: number | string; [controller: string]: number | string | undefined }
>;
export interface AppgateResultList<T> {
range?: string;
totalCount?: number;
data: T[];
}
// ─── Sync orchestration ───────────────────────────────────────────────────
export interface AppgateSyncResult {
syncId: string;
syncType: 'sessions' | 'daily' | 'full';
startedAt: Date;
completedAt: Date;
durationMs: number;
status: 'completed' | 'failed';
sessions: number;
devices: { upserted: number; tombstoned: number };
appliances: { upserted: number; tombstoned: number };
licenseUsers: number;
loginsCaptured: number;
errors: string[];
}