diff --git a/app/api/openclaw/datto-rmm/alerts/open/route.ts b/app/api/openclaw/datto-rmm/alerts/open/route.ts new file mode 100644 index 0000000..eeac520 --- /dev/null +++ b/app/api/openclaw/datto-rmm/alerts/open/route.ts @@ -0,0 +1,78 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { validateOpenClawKey } from '@/lib/utils/openclaw-auth'; +import { Pool } from 'pg'; +import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory'; + +const pool = new Pool({ + host: process.env.POSTGRES_HOST, + port: parseInt(process.env.POSTGRES_PORT || '5432'), + database: process.env.POSTGRES_DB, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, +}); + +export async function GET(request: NextRequest) { + const authError = validateOpenClawKey(request); + if (authError) return authError; + + const params = request.nextUrl.searchParams; + const live = params.get('live') === 'true'; + const siteUid = params.get('siteUid'); + const deviceUid = params.get('deviceUid'); + const limit = Math.min(1000, Math.max(1, parseInt(params.get('limit') || '200', 10))); + + try { + if (live) { + const client = getDattoRMMClient(); + let alerts = await client.getAllOpenAlerts(); + + if (siteUid) alerts = alerts.filter((a: any) => a.siteUid === siteUid); + if (deviceUid) alerts = alerts.filter((a: any) => a.deviceUid === deviceUid); + + return NextResponse.json({ + data: alerts.slice(0, limit), + total: alerts.length, + source: 'live', + }); + } + + const conditions: string[] = ['resolved = false']; + const values: unknown[] = []; + + if (siteUid) { + values.push(siteUid); + conditions.push(`site_uid = $${values.length}`); + } + if (deviceUid) { + values.push(deviceUid); + conditions.push(`device_uid = $${values.length}`); + } + + values.push(limit); + const result = await pool.query( + `SELECT + alert_uid, alert_category, alert_type, alert_message_en, + device_uid, device_hostname, device_ip, device_os, device_description, + site_uid, site_name, + resolved, muted, ticket_number, + timestamp, platform, triggered, + alert_context, alert_monitor_info, + last_user, ping_target, + synced_at, updated_at + FROM datto_rmm_alerts + WHERE ${conditions.join(' AND ')} + ORDER BY timestamp DESC + LIMIT $${values.length}`, + values + ); + + return NextResponse.json({ + data: result.rows, + total: result.rowCount, + source: 'db', + }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + return NextResponse.json({ error: msg }, { status: 500 }); + } +} diff --git a/app/api/openclaw/datto-rmm/alerts/route.ts b/app/api/openclaw/datto-rmm/alerts/route.ts new file mode 100644 index 0000000..9a6c45f --- /dev/null +++ b/app/api/openclaw/datto-rmm/alerts/route.ts @@ -0,0 +1,96 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { validateOpenClawKey } from '@/lib/utils/openclaw-auth'; +import { Pool } from 'pg'; +import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory'; + +const pool = new Pool({ + host: process.env.POSTGRES_HOST, + port: parseInt(process.env.POSTGRES_PORT || '5432'), + database: process.env.POSTGRES_DB, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, +}); + +export async function GET(request: NextRequest) { + const authError = validateOpenClawKey(request); + if (authError) return authError; + + const params = request.nextUrl.searchParams; + const live = params.get('live') === 'true'; + const resolved = params.get('resolved'); + const siteUid = params.get('siteUid'); + const deviceUid = params.get('deviceUid'); + const limit = Math.min(1000, Math.max(1, parseInt(params.get('limit') || '200', 10))); + + try { + if (live) { + const client = getDattoRMMClient(); + const openAlerts = await client.getAllOpenAlerts(); + let alerts: typeof openAlerts = []; + + if (resolved === 'true') { + const resolvedAlerts = await client.getRecentResolvedAlerts(); + alerts = resolvedAlerts as typeof openAlerts; + } else if (resolved === 'false' || resolved === null) { + alerts = openAlerts; + } else { + const resolvedAlerts = await client.getRecentResolvedAlerts(); + alerts = [...openAlerts, ...(resolvedAlerts as typeof openAlerts)]; + } + + if (siteUid) alerts = alerts.filter((a: any) => a.siteUid === siteUid); + if (deviceUid) alerts = alerts.filter((a: any) => a.deviceUid === deviceUid); + + return NextResponse.json({ + data: alerts.slice(0, limit), + total: alerts.length, + source: 'live', + }); + } + + const conditions: string[] = []; + const values: unknown[] = []; + + if (resolved !== null) { + values.push(resolved === 'true'); + conditions.push(`resolved = $${values.length}`); + } + if (siteUid) { + values.push(siteUid); + conditions.push(`site_uid = $${values.length}`); + } + if (deviceUid) { + values.push(deviceUid); + conditions.push(`device_uid = $${values.length}`); + } + + const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''; + + values.push(limit); + const result = await pool.query( + `SELECT + alert_uid, alert_category, alert_type, alert_message_en, + device_uid, device_hostname, device_ip, device_os, device_description, + site_uid, site_name, + resolved, resolved_by, resolved_on, muted, + ticket_number, timestamp, platform, triggered, + alert_context, alert_monitor_info, + last_user, ping_target, + synced_at, updated_at + FROM datto_rmm_alerts + ${where} + ORDER BY timestamp DESC + LIMIT $${values.length}`, + values + ); + + return NextResponse.json({ + data: result.rows, + total: result.rowCount, + source: 'db', + }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + return NextResponse.json({ error: msg }, { status: 500 }); + } +} diff --git a/app/api/openclaw/datto-rmm/devices/[uid]/audit/route.ts b/app/api/openclaw/datto-rmm/devices/[uid]/audit/route.ts new file mode 100644 index 0000000..6337afc --- /dev/null +++ b/app/api/openclaw/datto-rmm/devices/[uid]/audit/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { validateOpenClawKey } from '@/lib/utils/openclaw-auth'; +import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory'; + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ uid: string }> } +) { + const authError = validateOpenClawKey(request); + if (authError) return authError; + + const { uid } = await params; + + try { + const client = getDattoRMMClient(); + const audit = await client.getDeviceAudit(uid); + + if (!audit) { + return NextResponse.json( + { error: 'Audit data not found for this device' }, + { status: 404 } + ); + } + + return NextResponse.json({ data: audit, source: 'live' }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + return NextResponse.json({ error: msg }, { status: 500 }); + } +} diff --git a/app/api/openclaw/datto-rmm/devices/[uid]/route.ts b/app/api/openclaw/datto-rmm/devices/[uid]/route.ts new file mode 100644 index 0000000..1f7051f --- /dev/null +++ b/app/api/openclaw/datto-rmm/devices/[uid]/route.ts @@ -0,0 +1,61 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { validateOpenClawKey } from '@/lib/utils/openclaw-auth'; +import { Pool } from 'pg'; +import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory'; + +const pool = new Pool({ + host: process.env.POSTGRES_HOST, + port: parseInt(process.env.POSTGRES_PORT || '5432'), + database: process.env.POSTGRES_DB, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, +}); + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ uid: string }> } +) { + const authError = validateOpenClawKey(request); + if (authError) return authError; + + const { uid } = await params; + const live = request.nextUrl.searchParams.get('live') === 'true'; + + try { + if (live) { + const client = getDattoRMMClient(); + const device = await client.getDeviceById(uid); + if (!device) { + return NextResponse.json({ error: 'Device not found' }, { status: 404 }); + } + return NextResponse.json({ data: device, source: 'live' }); + } + + const result = await pool.query( + `SELECT + id, uid, site_id, site_uid, site_name, hostname, description, + device_type_category, device_type, operating_system, domain, + int_ip_address, ext_ip_address, last_logged_in_user, + last_seen, last_reboot, last_audit_date, creation_date, + online, suspended, deleted, reboot_required, a64_bit, + cag_version, display_version, + antivirus_product, antivirus_status, + patch_status, patches_approved_pending, patches_not_approved, patches_installed, + software_status, portal_url, web_remote_url, warranty_date, + snmp_enabled, device_class, network_probe, + udf, synced_at, updated_at + FROM datto_rmm_devices + WHERE uid = $1`, + [uid] + ); + + if (result.rowCount === 0) { + return NextResponse.json({ error: 'Device not found' }, { status: 404 }); + } + + return NextResponse.json({ data: result.rows[0], source: 'db' }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + return NextResponse.json({ error: msg }, { status: 500 }); + } +} diff --git a/app/api/openclaw/datto-rmm/devices/route.ts b/app/api/openclaw/datto-rmm/devices/route.ts new file mode 100644 index 0000000..11cbd8b --- /dev/null +++ b/app/api/openclaw/datto-rmm/devices/route.ts @@ -0,0 +1,110 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { validateOpenClawKey } from '@/lib/utils/openclaw-auth'; +import { Pool } from 'pg'; +import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory'; + +const pool = new Pool({ + host: process.env.POSTGRES_HOST, + port: parseInt(process.env.POSTGRES_PORT || '5432'), + database: process.env.POSTGRES_DB, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, +}); + +export async function GET(request: NextRequest) { + const authError = validateOpenClawKey(request); + if (authError) return authError; + + const params = request.nextUrl.searchParams; + const live = params.get('live') === 'true'; + const siteUid = params.get('siteUid'); + const online = params.get('online'); + const deleted = params.get('deleted'); + const page = Math.max(1, parseInt(params.get('page') || '1', 10)); + const limit = Math.min(500, Math.max(1, parseInt(params.get('limit') || '100', 10))); + const offset = (page - 1) * limit; + + try { + if (live) { + const client = getDattoRMMClient(); + let devices = siteUid + ? await client.getDevicesBySite(siteUid) + : await client.getAllDevices(); + + if (online !== null) { + const onlineBool = online === 'true'; + devices = devices.filter(d => d.online === onlineBool); + } + if (deleted !== null) { + const deletedBool = deleted === 'true'; + devices = devices.filter(d => d.deleted === deletedBool); + } + + const total = devices.length; + const paged = devices.slice(offset, offset + limit); + + return NextResponse.json({ + data: paged, + total, + page, + limit, + source: 'live', + }); + } + + const conditions: string[] = []; + const values: unknown[] = []; + + if (siteUid) { + values.push(siteUid); + conditions.push(`site_uid = $${values.length}`); + } + if (online !== null) { + values.push(online === 'true'); + conditions.push(`online = $${values.length}`); + } + if (deleted !== null) { + values.push(deleted === 'true'); + conditions.push(`deleted = $${values.length}`); + } + + const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''; + + const countResult = await pool.query( + `SELECT COUNT(*) FROM datto_rmm_devices ${where}`, + values + ); + const total = parseInt(countResult.rows[0].count, 10); + + values.push(limit, offset); + const dataResult = await pool.query( + `SELECT + id, uid, site_id, site_uid, site_name, hostname, description, + device_type_category, device_type, operating_system, domain, + int_ip_address, ext_ip_address, last_logged_in_user, + last_seen, last_reboot, last_audit_date, creation_date, + online, suspended, deleted, reboot_required, + cag_version, display_version, + antivirus_product, antivirus_status, + patch_status, patches_approved_pending, patches_not_approved, patches_installed, + portal_url, web_remote_url, warranty_date, device_class, + udf, synced_at, updated_at + FROM datto_rmm_devices + ${where} + ORDER BY hostname + LIMIT $${values.length - 1} OFFSET $${values.length}`, + values + ); + + return NextResponse.json({ + data: dataResult.rows, + total, + page, + limit, + source: 'db', + }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + return NextResponse.json({ error: msg }, { status: 500 }); + } +} diff --git a/app/api/openclaw/datto-rmm/sites/route.ts b/app/api/openclaw/datto-rmm/sites/route.ts new file mode 100644 index 0000000..aaf3e35 --- /dev/null +++ b/app/api/openclaw/datto-rmm/sites/route.ts @@ -0,0 +1,51 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { validateOpenClawKey } from '@/lib/utils/openclaw-auth'; +import { Pool } from 'pg'; +import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory'; + +const pool = new Pool({ + host: process.env.POSTGRES_HOST, + port: parseInt(process.env.POSTGRES_PORT || '5432'), + database: process.env.POSTGRES_DB, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, +}); + +export async function GET(request: NextRequest) { + const authError = validateOpenClawKey(request); + if (authError) return authError; + + const live = request.nextUrl.searchParams.get('live') === 'true'; + + try { + if (live) { + const client = getDattoRMMClient(); + const sites = await client.getAllSites(); + return NextResponse.json({ + data: sites, + total: sites.length, + source: 'live', + }); + } + + const result = await pool.query(` + SELECT + id, uid, name, description, notes, on_demand, + autotask_company_id, autotask_company_name, + number_of_devices, number_of_online_devices, number_of_offline_devices, + portal_url, synced_at, updated_at + FROM datto_rmm_sites + ORDER BY name + `); + + return NextResponse.json({ + data: result.rows, + total: result.rowCount, + source: 'db', + synced_at: result.rows[0]?.synced_at ?? null, + }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + return NextResponse.json({ error: msg }, { status: 500 }); + } +}