Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
943 lines
38 KiB
Markdown
943 lines
38 KiB
Markdown
# NinjaOne RMM Integration Plan
|
||
|
||
## Overview
|
||
|
||
This plan details the implementation of a comprehensive NinjaOne RMM integration for Alga PSA, enabling MSPs to synchronize device assets, receive real-time alerts, create tickets from RMM events, and initiate remote access sessions directly from the PSA interface.
|
||
|
||
### Core Features
|
||
- **Device Synchronization**: Bidirectional sync of devices/assets between NinjaOne and Alga PSA
|
||
- **Webhook Callbacks**: Real-time notifications when changes occur in NinjaOne
|
||
- **Asset-to-Ticket Linking**: Attach assets to tickets to track which device a ticket is for
|
||
- **Alert-to-Ticket Automation**: Receive alerts and automatically create tickets based on configurable rules
|
||
- **Remote Access**: Launch remote sessions to devices through NinjaOne from within Alga PSA
|
||
- **Patch Compliance Tracking**: Monitor and display patch status for managed devices
|
||
- **Software Inventory**: Sync and display installed software from RMM-managed devices
|
||
|
||
### NinjaOne API Capabilities (Reference)
|
||
|
||
| Category | Key Endpoints | Integration Use |
|
||
|----------|--------------|-----------------|
|
||
| **Devices** | `GET /devices`, `GET /device/{id}` | Core asset sync |
|
||
| **Alerts** | `GET /alerts`, `GET /device/{id}/alerts` | Ticket creation |
|
||
| **Webhooks** | `PUT /webhook` | Real-time sync |
|
||
| **Organizations** | `GET /organizations` | Client mapping |
|
||
| **Device Link** | `GET /device/{id}/dashboard-url` | Remote access |
|
||
| **Queries** | Health report, Software, Patches | Enhanced data |
|
||
|
||
**Webhook Event Types**: `NODE_CREATED`, `NODE_UPDATED`, `NODE_DELETED`, `TRIGGERED` (alerts), `RESET`, plus 200+ activity types including hardware changes, software changes, patch events, and antivirus alerts.
|
||
|
||
---
|
||
|
||
## Asset System Readiness Assessment
|
||
|
||
### Already Supported (No Changes Needed)
|
||
- Multi-tenant architecture with RLS policies
|
||
- Extension tables for asset types (workstation, server, network device, mobile, printer)
|
||
- Asset-to-ticket associations (`asset_ticket_associations`)
|
||
- External entity mapping table (`tenant_external_entity_mappings`)
|
||
- Asset history/audit trail
|
||
- Maintenance scheduling system
|
||
|
||
### Requires Enhancement
|
||
- Agent status tracking (online/offline, last seen)
|
||
- RMM alert storage and lifecycle management
|
||
- Remote access URL storage/retrieval
|
||
- Patch compliance fields
|
||
- Asset event types in EventBus
|
||
|
||
---
|
||
|
||
## Integration Settings UI Redesign
|
||
|
||
As integrations grow (QBO, Xero, Google Calendar, Microsoft Calendar, Email, and now NinjaOne), the current flat list within the Integrations tab becomes unwieldy. This plan includes a reorganization.
|
||
|
||
### Current Structure (SettingsPage.tsx)
|
||
```
|
||
Settings > Integrations Tab
|
||
├── QboIntegrationSettings (Card)
|
||
├── XeroIntegrationSettings (Card)
|
||
├── Inbound Email Integration (Card)
|
||
└── Calendar Integrations (Card)
|
||
```
|
||
|
||
### Proposed Structure
|
||
```
|
||
Settings > Integrations Tab
|
||
├── Integration Categories (Accordion or Sub-tabs)
|
||
│ ├── Accounting
|
||
│ │ ├── QuickBooks Online
|
||
│ │ └── Xero
|
||
│ ├── RMM & Endpoint Management
|
||
│ │ └── NinjaOne (NEW)
|
||
│ ├── Email & Communication
|
||
│ │ ├── Inbound Email
|
||
│ │ └── (Future: Outbound SMTP)
|
||
│ └── Calendar & Scheduling
|
||
│ ├── Google Calendar
|
||
│ └── Microsoft Calendar
|
||
└── (Future: PSA-to-PSA migrations)
|
||
```
|
||
|
||
### UI Implementation Tasks
|
||
- [x] Create `IntegrationCategory.tsx` component with collapsible sections
|
||
- [x] Create `IntegrationCard.tsx` reusable component extracting common patterns from QBO/Xero
|
||
- [x] Refactor `SettingsPage.tsx` Integrations tab to use category-based layout
|
||
- [x] Add category icons (accounting, RMM, email, calendar)
|
||
- [ ] Ensure responsive layout for mobile/tablet views
|
||
|
||
---
|
||
|
||
## Phased Implementation Plan
|
||
|
||
### Phase 0 – Database Schema & Foundation
|
||
|
||
#### Schema: RMM Integration Configuration
|
||
- [x] Create migration `YYYYMMDDHHMMSS_create_rmm_integration_tables.cjs`
|
||
- [x] Create `rmm_integrations` table:
|
||
```sql
|
||
CREATE TABLE rmm_integrations (
|
||
tenant UUID NOT NULL,
|
||
integration_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
integration_type TEXT NOT NULL DEFAULT 'ninjaone',
|
||
display_name TEXT NOT NULL,
|
||
api_instance TEXT NOT NULL, -- 'app' | 'eu' | 'oc' | 'ca' (region)
|
||
client_id TEXT NOT NULL,
|
||
is_active BOOLEAN DEFAULT true,
|
||
sync_enabled BOOLEAN DEFAULT true,
|
||
sync_interval_minutes INTEGER DEFAULT 60,
|
||
last_full_sync_at TIMESTAMPTZ,
|
||
last_incremental_sync_at TIMESTAMPTZ,
|
||
webhook_secret TEXT,
|
||
webhook_registered_at TIMESTAMPTZ,
|
||
settings JSONB DEFAULT '{}', -- Additional config options
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||
CONSTRAINT fk_tenant FOREIGN KEY (tenant) REFERENCES tenants(tenant)
|
||
);
|
||
CREATE INDEX idx_rmm_integrations_tenant ON rmm_integrations(tenant);
|
||
```
|
||
- [x] Create `rmm_organization_mappings` table for NinjaOne org → Alga client mapping:
|
||
```sql
|
||
CREATE TABLE rmm_organization_mappings (
|
||
tenant UUID NOT NULL,
|
||
mapping_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
integration_id UUID NOT NULL REFERENCES rmm_integrations(integration_id) ON DELETE CASCADE,
|
||
external_org_id TEXT NOT NULL, -- NinjaOne organization ID
|
||
external_org_name TEXT,
|
||
client_id UUID REFERENCES companies(company_id), -- Alga client
|
||
auto_sync_devices BOOLEAN DEFAULT true,
|
||
last_synced_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
UNIQUE(tenant, integration_id, external_org_id)
|
||
);
|
||
```
|
||
- [x] Create `rmm_alerts` table:
|
||
```sql
|
||
CREATE TABLE rmm_alerts (
|
||
tenant UUID NOT NULL,
|
||
alert_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
integration_id UUID REFERENCES rmm_integrations(integration_id) ON DELETE CASCADE,
|
||
external_alert_id TEXT NOT NULL,
|
||
external_device_id TEXT NOT NULL,
|
||
asset_id UUID REFERENCES assets(asset_id) ON DELETE SET NULL,
|
||
severity TEXT NOT NULL, -- NONE, MINOR, MODERATE, MAJOR, CRITICAL
|
||
priority TEXT, -- NONE, LOW, MEDIUM, HIGH
|
||
activity_type TEXT NOT NULL,
|
||
status_code TEXT NOT NULL,
|
||
message TEXT,
|
||
source_data JSONB, -- Full webhook payload
|
||
triggered_at TIMESTAMPTZ NOT NULL,
|
||
acknowledged_at TIMESTAMPTZ,
|
||
acknowledged_by UUID REFERENCES users(user_id),
|
||
resolved_at TIMESTAMPTZ,
|
||
resolved_by UUID REFERENCES users(user_id),
|
||
ticket_id UUID REFERENCES tickets(ticket_id) ON DELETE SET NULL,
|
||
auto_ticket_created BOOLEAN DEFAULT false,
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
UNIQUE(tenant, integration_id, external_alert_id)
|
||
);
|
||
CREATE INDEX idx_rmm_alerts_asset ON rmm_alerts(asset_id);
|
||
CREATE INDEX idx_rmm_alerts_ticket ON rmm_alerts(ticket_id);
|
||
CREATE INDEX idx_rmm_alerts_status ON rmm_alerts(tenant, status_code, triggered_at DESC);
|
||
```
|
||
- [x] Create `rmm_alert_rules` table for alert-to-ticket automation:
|
||
```sql
|
||
CREATE TABLE rmm_alert_rules (
|
||
tenant UUID NOT NULL,
|
||
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
integration_id UUID REFERENCES rmm_integrations(integration_id) ON DELETE CASCADE,
|
||
name TEXT NOT NULL,
|
||
is_active BOOLEAN DEFAULT true,
|
||
priority_order INTEGER DEFAULT 0,
|
||
conditions JSONB NOT NULL, -- { severity: [...], activityTypes: [...], orgIds: [...] }
|
||
actions JSONB NOT NULL, -- { createTicket: true, ticketPriority: 'high', assignToChannel: '...' }
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
#### Schema: Asset Table Enhancements
|
||
- [x] Add RMM-specific columns to `assets` table:
|
||
```sql
|
||
ALTER TABLE assets ADD COLUMN IF NOT EXISTS rmm_integration_id UUID REFERENCES rmm_integrations(integration_id) ON DELETE SET NULL;
|
||
ALTER TABLE assets ADD COLUMN IF NOT EXISTS rmm_device_id TEXT;
|
||
ALTER TABLE assets ADD COLUMN IF NOT EXISTS agent_status TEXT DEFAULT 'unknown'; -- online, offline, unknown
|
||
ALTER TABLE assets ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMPTZ;
|
||
ALTER TABLE assets ADD COLUMN IF NOT EXISTS remote_access_url TEXT;
|
||
ALTER TABLE assets ADD COLUMN IF NOT EXISTS rmm_sync_status TEXT DEFAULT 'pending'; -- synced, pending, error
|
||
ALTER TABLE assets ADD COLUMN IF NOT EXISTS rmm_last_synced_at TIMESTAMPTZ;
|
||
|
||
CREATE INDEX idx_assets_rmm_device ON assets(rmm_integration_id, rmm_device_id);
|
||
```
|
||
- [x] Add patch/compliance columns to workstation and server extension tables:
|
||
```sql
|
||
ALTER TABLE workstation_assets ADD COLUMN IF NOT EXISTS antivirus_status TEXT;
|
||
ALTER TABLE workstation_assets ADD COLUMN IF NOT EXISTS antivirus_product TEXT;
|
||
ALTER TABLE workstation_assets ADD COLUMN IF NOT EXISTS pending_os_patches INTEGER DEFAULT 0;
|
||
ALTER TABLE workstation_assets ADD COLUMN IF NOT EXISTS pending_software_patches INTEGER DEFAULT 0;
|
||
ALTER TABLE workstation_assets ADD COLUMN IF NOT EXISTS failed_patches INTEGER DEFAULT 0;
|
||
ALTER TABLE workstation_assets ADD COLUMN IF NOT EXISTS last_patch_scan_at TIMESTAMPTZ;
|
||
|
||
ALTER TABLE server_assets ADD COLUMN IF NOT EXISTS antivirus_status TEXT;
|
||
ALTER TABLE server_assets ADD COLUMN IF NOT EXISTS antivirus_product TEXT;
|
||
ALTER TABLE server_assets ADD COLUMN IF NOT EXISTS pending_os_patches INTEGER DEFAULT 0;
|
||
ALTER TABLE server_assets ADD COLUMN IF NOT EXISTS pending_software_patches INTEGER DEFAULT 0;
|
||
ALTER TABLE server_assets ADD COLUMN IF NOT EXISTS failed_patches INTEGER DEFAULT 0;
|
||
ALTER TABLE server_assets ADD COLUMN IF NOT EXISTS last_patch_scan_at TIMESTAMPTZ;
|
||
```
|
||
|
||
#### Schema: Update External Entity Mappings Usage
|
||
- [x] Document usage of existing `tenant_external_entity_mappings` for device ID mapping:
|
||
- `integration_type`: `'ninjaone'`
|
||
- `alga_entity_type`: `'asset'`
|
||
- `alga_entity_id`: Alga asset UUID
|
||
- `external_entity_id`: NinjaOne device ID (string)
|
||
- `external_realm_id`: NinjaOne organization ID (for scoping)
|
||
|
||
#### TypeScript Interfaces
|
||
- [x] Create `server/src/interfaces/rmm.interfaces.ts`:
|
||
- `RmmIntegration`, `RmmOrganizationMapping`, `RmmAlert`, `RmmAlertRule`
|
||
- `NinjaOneDevice`, `NinjaOneOrganization`, `NinjaOneAlert`
|
||
- `RmmConnectionStatus`, `RmmSyncStatus`
|
||
- [x] Update `server/src/interfaces/asset.interfaces.tsx` with RMM fields
|
||
|
||
#### Event Bus Extensions
|
||
- [x] Add RMM event types to `server/src/lib/eventBus/events.ts`:
|
||
```typescript
|
||
// RMM Integration Events
|
||
RMM_DEVICE_SYNCED = 'rmm.device.synced',
|
||
RMM_DEVICE_CREATED = 'rmm.device.created',
|
||
RMM_DEVICE_UPDATED = 'rmm.device.updated',
|
||
RMM_DEVICE_DELETED = 'rmm.device.deleted',
|
||
RMM_ALERT_RECEIVED = 'rmm.alert.received',
|
||
RMM_ALERT_ACKNOWLEDGED = 'rmm.alert.acknowledged',
|
||
RMM_ALERT_RESOLVED = 'rmm.alert.resolved',
|
||
RMM_SYNC_STARTED = 'rmm.sync.started',
|
||
RMM_SYNC_COMPLETED = 'rmm.sync.completed',
|
||
RMM_SYNC_FAILED = 'rmm.sync.failed',
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 1 – NinjaOne API Client & OAuth
|
||
|
||
#### OAuth Flow Implementation
|
||
- [x] Create `server/src/app/api/integrations/ninjaone/connect/route.ts`:
|
||
- Generate CSRF token using `crypto.randomBytes(16).toString('hex')`
|
||
- Create state payload with tenant ID and CSRF token
|
||
- Encode state as base64url
|
||
- Determine correct NinjaOne instance URL based on region selection
|
||
- Redirect to NinjaOne OAuth authorization endpoint
|
||
- OAuth scopes: `monitoring`, `management`, `control` (as needed)
|
||
- [x] Create `server/src/app/api/integrations/ninjaone/callback/route.ts`:
|
||
- Validate state parameter (decode, verify tenant, check CSRF)
|
||
- Exchange authorization code for access/refresh tokens
|
||
- Store tokens securely in tenant secrets via `secretProvider.setTenantSecret()`
|
||
- Create or update `rmm_integrations` record
|
||
- Redirect to settings page with success/error status params
|
||
- [x] Implement token refresh logic in API client
|
||
|
||
#### API Client
|
||
- [x] Create `server/src/lib/integrations/ninjaone/client.ts`:
|
||
```typescript
|
||
class NinjaOneClient {
|
||
constructor(tenantId: string, integrationId: string);
|
||
|
||
// Token management
|
||
private async getAccessToken(): Promise<string>;
|
||
private async refreshTokenIfNeeded(): Promise<void>;
|
||
|
||
// API methods
|
||
async getOrganizations(): Promise<NinjaOneOrganization[]>;
|
||
async getDevices(params?: DeviceQueryParams): Promise<NinjaOneDevice[]>;
|
||
async getDeviceDetails(deviceId: number): Promise<NinjaOneDeviceDetailed>;
|
||
async getDeviceAlerts(deviceId: number): Promise<NinjaOneAlert[]>;
|
||
async getAlerts(params?: AlertQueryParams): Promise<NinjaOneAlert[]>;
|
||
async getDeviceLink(deviceId: number): Promise<string>; // Remote access URL
|
||
async resetAlert(alertId: string): Promise<void>;
|
||
|
||
// Webhook management
|
||
async configureWebhook(webhookUrl: string, activities: string[]): Promise<void>;
|
||
async removeWebhook(): Promise<void>;
|
||
}
|
||
```
|
||
- [x] Create `server/src/lib/integrations/ninjaone/types.ts`:
|
||
- NinjaOne API response types
|
||
- Device, Organization, Alert, Activity types
|
||
- Webhook payload types
|
||
- [x] Create `server/src/lib/integrations/ninjaone/endpoints.ts`:
|
||
- API endpoint URL builders
|
||
- Region-specific base URLs (app, eu, oc, ca)
|
||
- [x] Create `server/src/lib/integrations/ninjaone/errors.ts`:
|
||
- Custom error classes for API errors
|
||
- Rate limiting handling
|
||
- Token expiration handling
|
||
|
||
#### Server Actions
|
||
- [x] Create `server/src/lib/actions/integrations/ninjaoneActions.ts`:
|
||
```typescript
|
||
// Connection management
|
||
export async function getNinjaOneConnectionStatus(): Promise<RmmConnectionStatus>;
|
||
export async function disconnectNinjaOne(integrationId: string): Promise<{ success: boolean }>;
|
||
export async function updateNinjaOneSettings(integrationId: string, settings: Partial<RmmIntegration>): Promise<void>;
|
||
|
||
// Organization mapping
|
||
export async function getNinjaOneOrganizations(integrationId: string): Promise<NinjaOneOrganization[]>;
|
||
export async function getOrganizationMappings(integrationId: string): Promise<RmmOrganizationMapping[]>;
|
||
export async function createOrganizationMapping(data: CreateOrgMappingRequest): Promise<RmmOrganizationMapping>;
|
||
export async function updateOrganizationMapping(mappingId: string, data: UpdateOrgMappingRequest): Promise<void>;
|
||
export async function deleteOrganizationMapping(mappingId: string): Promise<void>;
|
||
|
||
// Sync operations
|
||
export async function triggerFullSync(integrationId: string): Promise<{ jobId: string }>;
|
||
export async function getSyncStatus(integrationId: string): Promise<RmmSyncStatus>;
|
||
|
||
// Alert management
|
||
export async function getActiveAlerts(integrationId: string, filters?: AlertFilters): Promise<RmmAlert[]>;
|
||
export async function acknowledgeAlert(alertId: string): Promise<void>;
|
||
export async function createTicketFromAlert(alertId: string, options?: CreateTicketOptions): Promise<{ ticketId: string }>;
|
||
|
||
// Remote access
|
||
export async function getRemoteAccessUrl(assetId: string): Promise<{ url: string; expiresAt: string }>;
|
||
```
|
||
- [x] Implement RBAC checks using existing `hasPermission()` pattern
|
||
- [x] Use `system_settings` or new `rmm_settings` resource for permissions
|
||
|
||
#### Credential Storage
|
||
- [x] Store credentials in tenant secrets:
|
||
- Key: `ninjaone_credentials`
|
||
- Value: JSON with `{ [integrationId]: { accessToken, refreshToken, expiresAt, instanceUrl } }`
|
||
- [x] Implement credential retrieval with automatic token refresh
|
||
- [x] Add credential validation on connection status check
|
||
|
||
---
|
||
|
||
### Phase 2 – Integration Settings UI
|
||
|
||
#### Reusable Integration Components
|
||
- [x] Create `server/src/components/settings/integrations/IntegrationCard.tsx`:
|
||
- Reusable card component with status badge, connect/disconnect buttons
|
||
- Props: `title`, `description`, `status`, `onConnect`, `onDisconnect`, `children`
|
||
- Extract common patterns from `QboIntegrationSettings.tsx`
|
||
- [x] Create `server/src/components/settings/integrations/IntegrationCategory.tsx`:
|
||
- Collapsible category container with icon and title
|
||
- Props: `title`, `icon`, `defaultOpen`, `children`
|
||
- [x] Create `server/src/components/settings/integrations/ConnectionStatusBadge.tsx`:
|
||
- Unified status badge component
|
||
- States: connected, disconnected, error, syncing, expired
|
||
|
||
#### NinjaOne Settings Component
|
||
- [x] Create `server/src/components/settings/integrations/NinjaOneIntegrationSettings.tsx`:
|
||
- Connection status display with last sync time
|
||
- Connect button with region selector (North America, EMEA, APAC, Canada)
|
||
- Disconnect confirmation modal
|
||
- Sync settings (interval, auto-sync toggle)
|
||
- Link to organization mapping
|
||
- Link to alert rules configuration
|
||
- [x] Create `server/src/components/settings/integrations/NinjaOneDisconnectModal.tsx`:
|
||
- Confirmation dialog following `QboDisconnectConfirmModal` pattern
|
||
- Warning about data that will be affected
|
||
- [x] Create `server/src/components/settings/integrations/NinjaOneRegionSelector.tsx`:
|
||
- Dropdown for selecting NinjaOne instance region
|
||
- Display region-specific information
|
||
|
||
#### Organization Mapping UI
|
||
- [x] Create `server/src/components/settings/integrations/ninjaone/OrganizationMappingManager.tsx`:
|
||
- List of NinjaOne organizations with mapping status
|
||
- Dropdown to select corresponding Alga client for each org
|
||
- Auto-sync toggle per organization
|
||
- Bulk mapping actions
|
||
- [x] Create `server/src/components/settings/integrations/ninjaone/OrganizationMappingRow.tsx`:
|
||
- Individual row component for org mapping
|
||
- Status indicators (mapped, unmapped, sync error)
|
||
|
||
#### Alert Rules UI
|
||
- [x] Create `server/src/components/settings/integrations/ninjaone/AlertRulesManager.tsx`:
|
||
- List of configured alert-to-ticket rules
|
||
- Add/Edit/Delete rule actions
|
||
- Rule priority ordering (drag-and-drop)
|
||
- [x] Create `server/src/components/settings/integrations/ninjaone/AlertRuleForm.tsx`:
|
||
- Rule name and active toggle
|
||
- Condition builder:
|
||
- Severity filter (multi-select)
|
||
- Activity type filter (multi-select with search)
|
||
- Organization filter (optional)
|
||
- Action configuration:
|
||
- Create ticket toggle
|
||
- Ticket priority mapping
|
||
- Channel/team assignment
|
||
- Notification settings
|
||
|
||
#### Settings Page Integration
|
||
- [x] Refactor `server/src/components/settings/general/SettingsPage.tsx`:
|
||
- Implement category-based layout using `IntegrationCategory`
|
||
- Add "RMM & Endpoint Management" category
|
||
- Include `NinjaOneIntegrationSettings` in RMM category
|
||
- Reorganize existing integrations into categories
|
||
|
||
---
|
||
|
||
### Phase 3 – Device Synchronization
|
||
|
||
#### Device Mapper
|
||
- [x] Create `server/src/lib/integrations/ninjaone/mappers/deviceMapper.ts`:
|
||
```typescript
|
||
export function mapNinjaOneDeviceToAsset(
|
||
device: NinjaOneDeviceDetailed,
|
||
existingAsset?: Asset
|
||
): CreateAssetRequest | UpdateAssetRequest;
|
||
|
||
export function determineAssetType(device: NinjaOneDevice): AssetType;
|
||
// nodeClass mapping: WINDOWS_WORKSTATION → workstation, WINDOWS_SERVER → server, etc.
|
||
|
||
export function mapDeviceHardware(device: NinjaOneDeviceDetailed): WorkstationExtension | ServerExtension;
|
||
|
||
export function mapNetworkInterfaces(interfaces: NinjaOneNetworkInterface[]): NetworkInterfaceData[];
|
||
```
|
||
- [ ] Field mapping reference:
|
||
| NinjaOne Field | Alga Asset Field |
|
||
|----------------|------------------|
|
||
| `id` | `rmm_device_id` |
|
||
| `systemName` | `name` |
|
||
| `nodeClass` | `asset_type` (mapped) |
|
||
| `organizationId` | `client_id` (via org mapping) |
|
||
| `offline` | `agent_status` |
|
||
| `lastContact` | `last_seen_at` |
|
||
| `system.manufacturer` | `attributes.manufacturer` |
|
||
| `system.model` | `attributes.model` |
|
||
| `os.name` + `os.version` | `os_type`, `os_version` |
|
||
| `processors[0]` | `cpu_model`, `cpu_cores` |
|
||
| `memory.capacity` | `ram_gb` |
|
||
| `volumes[].capacity` | `storage_capacity` (sum) |
|
||
|
||
#### Sync Engine
|
||
- [x] Create `server/src/lib/integrations/ninjaone/sync/syncEngine.ts`:
|
||
```typescript
|
||
export class NinjaOneSyncEngine {
|
||
constructor(tenantId: string, integrationId: string);
|
||
|
||
async runFullSync(): Promise<SyncResult>;
|
||
async runIncrementalSync(since: Date): Promise<SyncResult>;
|
||
async syncDevice(deviceId: number): Promise<Asset>;
|
||
async syncOrganization(orgId: number): Promise<SyncResult>;
|
||
|
||
private async createAsset(device: NinjaOneDevice): Promise<Asset>;
|
||
private async updateAsset(asset: Asset, device: NinjaOneDevice): Promise<Asset>;
|
||
private async handleDeletedDevice(deviceId: number): Promise<void>;
|
||
}
|
||
```
|
||
- [x] Create `server/src/lib/integrations/ninjaone/sync/syncJob.ts`:
|
||
- Background job for scheduled sync
|
||
- Progress tracking and reporting
|
||
- Error handling and retry logic
|
||
- Emit events for sync lifecycle
|
||
- [x] Implement conflict resolution:
|
||
- Last-write-wins for most fields
|
||
- Preserve manual Alga edits for certain fields (notes, custom attributes)
|
||
- Log conflicts for review
|
||
|
||
#### Initial Sync Flow
|
||
- [x] Create sync initiation endpoint/action
|
||
- [x] Implement pagination for large device sets (NinjaOne uses cursor-based pagination)
|
||
- [x] Track sync progress in `rmm_integrations.settings` JSONB
|
||
- [x] Create sync history/log table or use existing job tracking
|
||
|
||
#### Sync Scheduling
|
||
- [x] Implement configurable sync interval (default: 60 minutes)
|
||
- [x] Create cron job or Temporal workflow for scheduled sync
|
||
- [x] Add manual "Sync Now" button in UI
|
||
- [x] Implement sync locking to prevent concurrent syncs
|
||
|
||
---
|
||
|
||
### Phase 4 – Webhook Handler & Real-Time Sync
|
||
|
||
#### Webhook Endpoint
|
||
- [x] Create `server/src/app/api/webhooks/ninjaone/route.ts`:
|
||
```typescript
|
||
export async function POST(request: Request) {
|
||
// 1. Verify webhook signature (HMAC)
|
||
// 2. Parse webhook payload
|
||
// 3. Identify tenant from integration lookup
|
||
// 4. Route to appropriate handler based on activityType
|
||
// 5. Return 200 quickly, process async
|
||
}
|
||
```
|
||
- [x] Implement webhook signature verification:
|
||
- NinjaOne sends signature in header
|
||
- Verify using stored webhook secret
|
||
- [x] Create `server/src/lib/integrations/ninjaone/webhooks/webhookHandler.ts`:
|
||
```typescript
|
||
export async function handleNinjaOneWebhook(payload: NinjaOneWebhookPayload): Promise<void>;
|
||
|
||
export async function handleDeviceEvent(payload: DeviceActivityPayload): Promise<void>;
|
||
export async function handleAlertEvent(payload: AlertActivityPayload): Promise<void>;
|
||
export async function handleSystemEvent(payload: SystemActivityPayload): Promise<void>;
|
||
```
|
||
|
||
#### Webhook Event Handlers
|
||
- [x] Device lifecycle events:
|
||
- `NODE_CREATED`: Create new asset in Alga
|
||
- `NODE_UPDATED`: Update existing asset
|
||
- `NODE_DELETED`: Mark asset as inactive or delete
|
||
- `NODE_MANUALLY_APPROVED`: Activate newly approved device
|
||
- [x] Hardware change events:
|
||
- `CPU_ADDED`, `CPU_REMOVED`
|
||
- `MEMORY_ADDED`, `MEMORY_REMOVED`
|
||
- `DISK_DRIVE_ADDED`, `DISK_DRIVE_REMOVED`
|
||
- Update extension table fields
|
||
- [x] Status events:
|
||
- `SYSTEM_REBOOTED`: Update last_seen, log event
|
||
- `USER_LOGGED_IN`, `USER_LOGGED_OUT`: Update last_login in extension
|
||
- [x] Alert events (CONDITION type with TRIGGERED/RESET status):
|
||
- Create `rmm_alerts` record
|
||
- Evaluate alert rules for auto-ticket creation
|
||
- Emit `RMM_ALERT_RECEIVED` event
|
||
|
||
#### Webhook Registration
|
||
- [x] Add webhook configuration to connection flow
|
||
- [x] Create webhook URL with tenant-specific path or token
|
||
- [x] Register webhook with NinjaOne API on connection
|
||
- [x] Remove webhook on disconnect
|
||
- [x] Handle webhook secret rotation
|
||
|
||
#### Async Processing
|
||
- [x] Queue webhook payloads for async processing (avoid timeout)
|
||
- [x] Implement idempotency using external_alert_id/activity id
|
||
- [x] Add retry logic for transient failures
|
||
- [x] Create webhook processing log for debugging
|
||
|
||
---
|
||
|
||
### Phase 4.5 – Webhook Auto-Registration
|
||
|
||
NinjaOne supports programmatic webhook configuration via the API. This allows us to automatically register the Alga webhook endpoint after OAuth authentication, eliminating manual configuration in the NinjaOne UI.
|
||
|
||
#### API Reference
|
||
- **`PUT /v2/webhook`** - Configure webhook endpoint and activity filters
|
||
- **`DELETE /v2/webhook`** - Remove webhook configuration
|
||
|
||
#### Webhook Registration Service
|
||
- [ ] Create `ee/server/src/lib/integrations/ninjaone/webhooks/webhookRegistration.ts`:
|
||
```typescript
|
||
export interface WebhookConfig {
|
||
url: string;
|
||
activities: {
|
||
statusCode?: string[]; // Filter by status codes
|
||
activityType?: string[]; // Filter by activity types
|
||
};
|
||
expand?: string[]; // References to expand in payloads (device, organization)
|
||
headers?: Array<{ name: string; value: string }>; // Custom auth headers
|
||
organizationIds?: number[]; // Optional org filter
|
||
}
|
||
|
||
export async function registerWebhook(
|
||
client: NinjaOneClient,
|
||
config: WebhookConfig
|
||
): Promise<void>;
|
||
|
||
export async function removeWebhook(
|
||
client: NinjaOneClient
|
||
): Promise<void>;
|
||
|
||
export function getDefaultWebhookConfig(
|
||
baseUrl: string,
|
||
webhookSecret: string
|
||
): WebhookConfig;
|
||
```
|
||
|
||
#### Default Activity Subscriptions
|
||
Configure webhook to receive the most useful events:
|
||
- **Device Lifecycle**: `NODE_CREATED`, `NODE_UPDATED`, `NODE_DELETED`, `NODE_MANUALLY_APPROVED`, `NODE_AUTOMATICALLY_APPROVED`
|
||
- **Alerts/Conditions**: `TRIGGERED`, `RESET`, `ACKNOWLEDGED`
|
||
- **System Events**: `SYSTEM_REBOOTED`, `USER_LOGGED_IN`, `USER_LOGGED_OUT`
|
||
- **Hardware Changes**: `CPU_ADDED`, `CPU_REMOVED`, `MEMORY_ADDED`, `MEMORY_REMOVED`, `DISK_DRIVE_ADDED`, `DISK_DRIVE_REMOVED`, `ADAPTER_ADDED`, `ADAPTER_REMOVED`
|
||
- **Patch Management**: `PATCH_MANAGEMENT_SCAN_STARTED`, `PATCH_MANAGEMENT_SCAN_COMPLETED`, `PATCH_MANAGEMENT_INSTALLED`, `PATCH_MANAGEMENT_INSTALL_FAILED`
|
||
- **Software Changes**: `SOFTWARE_ADDED`, `SOFTWARE_REMOVED`
|
||
- **Antivirus**: Severity-based alerts from supported AV products
|
||
|
||
#### Integration with OAuth Flow
|
||
- [ ] Update `ee/server/src/app/api/integrations/ninjaone/callback/route.ts`:
|
||
- After successful token exchange and storage
|
||
- Generate webhook secret using `crypto.randomBytes(32).toString('hex')`
|
||
- Store webhook secret in rmm_integrations settings
|
||
- Call `registerWebhook()` with tenant-specific callback URL
|
||
- Handle registration failures gracefully (log, retry later)
|
||
- Update `rmm_integrations.settings` with `webhookRegisteredAt` timestamp
|
||
|
||
#### Integration with Disconnect Flow
|
||
- [ ] Update `disconnectNinjaOneIntegration` action:
|
||
- Call `removeWebhook()` before clearing credentials
|
||
- Handle removal failures gracefully (webhook may already be removed)
|
||
- Clear webhook secret from settings
|
||
|
||
#### Webhook URL Configuration
|
||
- [ ] Add environment variable `NINJAONE_WEBHOOK_BASE_URL` for production URL
|
||
- [ ] Generate tenant-scoped webhook URL:
|
||
- Format: `{baseUrl}/api/webhooks/ninjaone?tenant={tenantId}`
|
||
- Or use signed token approach for security
|
||
- [ ] Update webhook handler to validate custom auth header
|
||
|
||
#### Error Handling & Retry
|
||
- [ ] Handle rate limiting (429 responses)
|
||
- [ ] Implement retry with exponential backoff
|
||
- [ ] Log registration failures for admin visibility
|
||
- [ ] Add "Re-register Webhook" button in settings UI for manual recovery
|
||
|
||
#### Testing
|
||
- [ ] Unit test webhook config generation
|
||
- [ ] Integration test webhook registration flow
|
||
- [ ] Test disconnect cleans up webhook
|
||
|
||
---
|
||
|
||
### Phase 5 – Alert Integration & Ticket Creation
|
||
|
||
#### Alert Processing
|
||
- [x] Create `server/src/lib/integrations/ninjaone/alerts/alertProcessor.ts`:
|
||
```typescript
|
||
export async function processAlert(
|
||
tenantId: string,
|
||
integrationId: string,
|
||
alertPayload: NinjaOneAlertPayload
|
||
): Promise<RmmAlert>;
|
||
|
||
export async function evaluateAlertRules(
|
||
tenantId: string,
|
||
alert: RmmAlert
|
||
): Promise<AlertRuleMatch | null>;
|
||
|
||
export async function executeAlertActions(
|
||
alert: RmmAlert,
|
||
rule: RmmAlertRule
|
||
): Promise<void>;
|
||
```
|
||
|
||
#### Ticket Creation from Alerts
|
||
- [x] Create `server/src/lib/integrations/ninjaone/alerts/ticketCreator.ts`:
|
||
```typescript
|
||
export async function createTicketFromAlert(
|
||
alert: RmmAlert,
|
||
options: CreateTicketFromAlertOptions
|
||
): Promise<Ticket>;
|
||
```
|
||
- [ ] Ticket content generation:
|
||
- Title: `[NinjaOne Alert] {activityType} on {deviceName}`
|
||
- Description: Alert details, device info, severity
|
||
- Priority mapping: CRITICAL → urgent, MAJOR → high, MODERATE → medium, MINOR → low
|
||
- Auto-link to asset via `asset_ticket_associations`
|
||
- Include device context (client, location, IP, last user)
|
||
- [x] Update `rmm_alerts` with ticket reference
|
||
|
||
#### Alert Management UI
|
||
- [ ] Create `server/src/components/alerts/RmmAlertsPanel.tsx`:
|
||
- List of active RMM alerts
|
||
- Filter by severity, status, device
|
||
- Acknowledge/resolve actions
|
||
- Create ticket action
|
||
- [ ] Add alerts indicator to asset detail drawer
|
||
- [ ] Create alert detail modal/drawer
|
||
|
||
#### Alert Acknowledgment & Resolution
|
||
- [ ] Implement acknowledge action (updates local record)
|
||
- [ ] Implement resolve action (optionally resets in NinjaOne via API)
|
||
- [ ] Track who acknowledged/resolved
|
||
- [ ] Sync acknowledgment state bidirectionally (optional)
|
||
|
||
---
|
||
|
||
### Phase 6 – Remote Access Integration
|
||
|
||
#### Remote Access URL Retrieval
|
||
- [x] Implement `getRemoteAccessUrl` in NinjaOne client:
|
||
- Call `GET /device/{id}/dashboard-url` endpoint
|
||
- Parse and return the remote access URL
|
||
- Handle cases where remote access is not available
|
||
- [ ] Cache URLs with short TTL (5-10 minutes)
|
||
- [x] Handle URL expiration gracefully
|
||
|
||
#### Remote Access UI
|
||
- [x] Add "Remote Connect" button to `AssetDetailDrawer.tsx`:
|
||
- Only show for RMM-managed assets
|
||
- Show loading state while fetching URL
|
||
- Open URL in new tab/window
|
||
- Handle errors (device offline, no remote access configured)
|
||
- [x] Create `server/src/components/assets/RemoteAccessButton.tsx`:
|
||
- Props: `assetId`, `disabled`, `variant`
|
||
- Handle click to fetch and open URL
|
||
- Show tooltip with device status
|
||
- [x] Add remote access to asset actions menu
|
||
- [x] Log remote access attempts for audit trail
|
||
|
||
#### Remote Access from Ticket Context
|
||
- [x] Add remote access button to ticket detail when asset is linked
|
||
- [x] Show linked asset's remote access status
|
||
- [ ] Quick action in ticket actions menu
|
||
|
||
---
|
||
|
||
### Phase 7 – Enhanced Asset Display
|
||
|
||
#### Asset Card Enhancements
|
||
- [x] Update `AssetCard.tsx` to show RMM status:
|
||
- Agent status indicator (online/offline badge)
|
||
- Last seen timestamp
|
||
- Sync status indicator
|
||
- Patch compliance badge (if applicable)
|
||
- [x] Add RMM source indicator for synced assets
|
||
- [ ] Show alert count badge when active alerts exist
|
||
|
||
#### Asset Detail Drawer Enhancements
|
||
- [x] Add "RMM Status" section to detail drawer:
|
||
- Agent status with last contact time
|
||
- Sync status and last sync time
|
||
- Link to NinjaOne dashboard
|
||
- Remote access button
|
||
- [x] Add "Active Alerts" section:
|
||
- List of unresolved alerts for this device
|
||
- Quick actions (acknowledge, create ticket)
|
||
- [x] Add "Patch Status" section:
|
||
- Pending patches count
|
||
- Failed patches count
|
||
- Last scan time
|
||
- Link to detailed patch report
|
||
- [x] Add "Software Inventory" section:
|
||
- List of installed software from RMM
|
||
- Version information
|
||
- Search functionality
|
||
|
||
#### Asset List Filters
|
||
- [x] Add RMM-related filters to asset list:
|
||
- Agent status (online, offline, unknown)
|
||
- RMM managed (yes, no)
|
||
- [ ] Has active alerts (future)
|
||
- [ ] Patch compliance status (future)
|
||
- [ ] Add bulk actions for RMM assets:
|
||
- Trigger sync for selected
|
||
- View in NinjaOne (bulk open)
|
||
|
||
---
|
||
|
||
### Phase 8 – Patch & Software Inventory Sync
|
||
|
||
#### Patch Status Sync
|
||
- [x] Create `ee/server/src/lib/integrations/ninjaone/sync/patchSync.ts`:
|
||
- Fetch pending/failed patches from NinjaOne
|
||
- Update extension table fields
|
||
- Track patch scan timestamps
|
||
- [x] Add server actions for patch sync (`triggerPatchStatusSync`)
|
||
- [ ] Add patch status to device sync flow (auto-sync during device sync)
|
||
- [ ] Create scheduled patch status refresh (less frequent than device sync)
|
||
|
||
#### Software Inventory Sync
|
||
- [x] Create `ee/server/src/lib/integrations/ninjaone/sync/softwareSync.ts`:
|
||
- Fetch installed software list
|
||
- Store in extension table `installed_software` JSONB field
|
||
- Track software changes in asset history
|
||
- [x] Implement software inventory UI component (`AssetSoftwareInventory.tsx`)
|
||
- [x] Add software search across assets (`searchSoftwareAcrossAssets`, `searchSoftware` action)
|
||
|
||
#### Compliance Dashboard
|
||
- [x] Create compliance summary widget for dashboard:
|
||
- Devices online/offline count
|
||
- Patches pending/failed count
|
||
- Active alerts count
|
||
- `NinjaOneComplianceDashboard.tsx` component
|
||
- [x] Add `getRmmComplianceSummary` server action
|
||
- [ ] Add compliance reporting (future)
|
||
|
||
#### Asset List RMM Filters
|
||
- [x] Add agent status filter (Online, Offline, Unknown)
|
||
- [x] Add RMM managed filter (Managed, Not Managed)
|
||
- [x] Display RMM filter pills in active filters bar
|
||
|
||
---
|
||
|
||
### Phase 9 – Testing & Documentation
|
||
|
||
#### Unit Tests
|
||
- [ ] Test NinjaOne API client methods
|
||
- [ ] Test device mapper transformations
|
||
- [ ] Test webhook signature verification
|
||
- [ ] Test alert rule evaluation
|
||
- [ ] Test ticket creation from alerts
|
||
|
||
#### Integration Tests
|
||
- [ ] Test OAuth flow (mock NinjaOne OAuth server)
|
||
- [ ] Test webhook endpoint with sample payloads
|
||
- [ ] Test full sync workflow
|
||
- [ ] Test alert-to-ticket flow
|
||
|
||
#### E2E Tests
|
||
- [ ] Test connection flow in UI
|
||
- [ ] Test organization mapping UI
|
||
- [ ] Test alert rules configuration
|
||
- [ ] Test remote access button functionality
|
||
|
||
#### Documentation
|
||
- [ ] Create user documentation for NinjaOne setup
|
||
- [ ] Document organization mapping best practices
|
||
- [ ] Document alert rule configuration
|
||
- [ ] Create troubleshooting guide
|
||
- [ ] Add API documentation for webhook endpoint
|
||
|
||
---
|
||
|
||
## File Structure Summary
|
||
|
||
```
|
||
server/src/
|
||
├── app/
|
||
│ └── api/
|
||
│ ├── integrations/
|
||
│ │ └── ninjaone/
|
||
│ │ ├── connect/route.ts
|
||
│ │ └── callback/route.ts
|
||
│ └── webhooks/
|
||
│ └── ninjaone/
|
||
│ └── route.ts
|
||
├── components/
|
||
│ └── settings/
|
||
│ └── integrations/
|
||
│ ├── IntegrationCard.tsx
|
||
│ ├── IntegrationCategory.tsx
|
||
│ ├── ConnectionStatusBadge.tsx
|
||
│ ├── NinjaOneIntegrationSettings.tsx
|
||
│ ├── NinjaOneDisconnectModal.tsx
|
||
│ ├── NinjaOneRegionSelector.tsx
|
||
│ └── ninjaone/
|
||
│ ├── OrganizationMappingManager.tsx
|
||
│ ├── OrganizationMappingRow.tsx
|
||
│ ├── AlertRulesManager.tsx
|
||
│ └── AlertRuleForm.tsx
|
||
├── lib/
|
||
│ ├── actions/
|
||
│ │ └── integrations/
|
||
│ │ └── ninjaoneActions.ts
|
||
│ └── integrations/
|
||
│ └── ninjaone/
|
||
│ ├── client.ts
|
||
│ ├── types.ts
|
||
│ ├── endpoints.ts
|
||
│ ├── errors.ts
|
||
│ ├── mappers/
|
||
│ │ └── deviceMapper.ts
|
||
│ ├── sync/
|
||
│ │ ├── syncEngine.ts
|
||
│ │ ├── syncJob.ts
|
||
│ │ ├── patchSync.ts
|
||
│ │ └── softwareSync.ts
|
||
│ ├── webhooks/
|
||
│ │ └── webhookHandler.ts
|
||
│ └── alerts/
|
||
│ ├── alertProcessor.ts
|
||
│ └── ticketCreator.ts
|
||
└── interfaces/
|
||
└── rmm.interfaces.ts
|
||
|
||
server/migrations/
|
||
└── YYYYMMDDHHMMSS_create_rmm_integration_tables.cjs
|
||
```
|
||
|
||
---
|
||
|
||
## Dependencies & Prerequisites
|
||
|
||
### External
|
||
- NinjaOne API application registration (OAuth client ID/secret)
|
||
- Webhook endpoint accessible from NinjaOne servers (public URL or tunnel for dev)
|
||
- NinjaOne account with API access enabled
|
||
|
||
### Internal
|
||
- Existing `tenant_external_entity_mappings` table
|
||
- Existing `ISecretProvider` for credential storage
|
||
- Existing asset management system
|
||
- Existing ticket system
|
||
- EventBus for event publishing
|
||
|
||
---
|
||
|
||
## Acceptance Criteria
|
||
|
||
### Phase 1-2 (Connection & UI)
|
||
- [x] User can connect NinjaOne account via OAuth from settings
|
||
- [x] Connection status displays correctly (connected, disconnected, error)
|
||
- [x] User can disconnect NinjaOne integration
|
||
- [x] Settings page shows integrations in organized categories
|
||
|
||
### Phase 3 (Device Sync)
|
||
- [x] Initial sync imports all devices from mapped organizations
|
||
- [x] Devices are created with correct asset type based on nodeClass
|
||
- [x] Hardware details (CPU, RAM, storage) are populated in extension tables
|
||
- [x] Scheduled sync runs at configured interval
|
||
- [x] Manual "Sync Now" works correctly
|
||
|
||
### Phase 4 (Webhooks)
|
||
- [x] Webhook endpoint receives and validates NinjaOne callbacks
|
||
- [x] Device changes in NinjaOne reflect in Alga within seconds
|
||
- [x] New devices are created automatically
|
||
- [x] Deleted devices are handled appropriately
|
||
|
||
### Phase 5 (Alerts & Tickets)
|
||
- [x] Alerts from NinjaOne are stored in rmm_alerts table
|
||
- [x] Alert rules can be configured via UI
|
||
- [x] Tickets are auto-created based on matching rules
|
||
- [x] Tickets include device context and are linked to asset
|
||
|
||
### Phase 6 (Remote Access)
|
||
- [x] "Remote Connect" button appears on RMM-synced assets
|
||
- [x] Clicking button opens NinjaOne remote session in new tab
|
||
- [ ] Remote access works from asset detail and ticket context
|
||
|
||
### Phase 7-8 (Enhanced Display & Compliance)
|
||
- [x] Asset cards show agent status and alert indicators
|
||
- [x] Asset detail shows comprehensive RMM status
|
||
- [x] Patch compliance status is displayed (AssetPatchStatusSection)
|
||
- [x] Software inventory is viewable (AssetSoftwareInventory)
|
||
- [x] Compliance dashboard widget shows fleet health
|
||
- [x] Asset list supports RMM filters (agent status, managed/unmanaged)
|
||
|
||
---
|
||
|
||
## Future Considerations
|
||
|
||
### Additional RMM Platforms
|
||
This architecture is designed to support multiple RMM platforms. Future integrations could include:
|
||
- Datto RMM
|
||
- ConnectWise Automate
|
||
- Syncro
|
||
- Atera
|
||
|
||
The `rmm_integrations` table and `integration_type` field support this extensibility.
|
||
|
||
### Bidirectional Sync Enhancements
|
||
- Push Alga asset changes to NinjaOne custom fields
|
||
- Sync maintenance windows bidirectionally
|
||
- Push ticket status updates to NinjaOne
|
||
|
||
### Advanced Features
|
||
- Script execution from Alga PSA
|
||
- Patch deployment initiation
|
||
- Software deployment
|
||
- Device group management
|