Files
Deep-Package-Inspection/src/lib/actions/agents.ts
T

282 lines
9.3 KiB
TypeScript

"use server";
if (typeof globalThis.crypto === 'undefined') {
globalThis.crypto = require('crypto');
}
import { revalidatePath } from "next/cache";
import { cookies } from "next/headers";
import jwt from "jsonwebtoken";
import mongoose from "mongoose";
const BASE_URL = process.env.BACKONE_BASE_URL || process.env.NETIFY_BASE_URL;
const API_URL = BASE_URL ? `${BASE_URL}/assets/agents` : "https://manager.netify.ai/api/v1/assets/agents";
const getApiKey = () => process.env.BACKONE_API_KEY || process.env.NETIFY_API_KEY;
const getJwtToken = () => process.env.BACKONE_JWT_TOKEN || process.env.NETIFY_JWT_TOKEN;
const getSiteUuid = () => process.env.BACKONE_SITE_UUID || process.env.NETIFY_SITE_UUID;
// Helper function to get headers
const getHeaders = () => {
const API_KEY = getApiKey();
const JWT_TOKEN = getJwtToken();
const SITE_UUID = getSiteUuid();
if (!API_KEY && !JWT_TOKEN) {
throw new Error("BACKONE_API_KEY or BACKONE_JWT_TOKEN is not set in environment variables");
}
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
// Prioritize long-term API Key as recommended by the DPI API support team
if (API_KEY) {
headers["x-api-key"] = API_KEY;
} else if (JWT_TOKEN) {
headers["x-api-key"] = JWT_TOKEN;
}
if (SITE_UUID) {
headers["x-net-site"] = SITE_UUID;
}
return headers;
};
export interface Agent {
id: number;
uuid: string;
serial: string;
site_uuid: string;
organization_uuid: string;
provisioned: boolean;
activated: boolean;
label?: string;
created_at: { human: string; date: string; unix_time: number };
updated_at: { human: string; date: string; unix_time: number };
last_seen_at: { human: string; date: string; unix_time: number };
}
export async function getAgents(clientSiteUuid?: string): Promise<Agent[]> {
try {
const API_KEY = getApiKey();
let SITE_UUID = clientSiteUuid || getSiteUuid();
// Securely retrieve the user's actual role and site_uuid on the server side
try {
const cookieStore = await cookies();
const token = cookieStore.get('token')?.value;
if (token) {
const decoded: any = jwt.verify(token, process.env.JWT_SECRET || 'super-secret-backone-key');
if (decoded && decoded.role === 'TENANT_ADMIN') {
// Force user's site_uuid to prevent TENANT_ADMIN from requesting other sites
SITE_UUID = decoded.site_uuid;
}
}
} catch (cookieErr) {
console.warn("Failed to retrieve or verify cookie token inside getAgents:", cookieErr);
}
// All agent data comes from MongoDB (single source of truth per Rule 13)
if (API_KEY && API_KEY.startsWith("sk_db_")) {
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/backone_dpi';
// Connect to MongoDB if not already connected
if (mongoose.connection.readyState === 0) {
await mongoose.connect(MONGODB_URI);
}
const db = mongoose.connection.db;
if (!db) {
throw new Error("Database connection not ready");
}
const filter: any = { agent_uuid: { $ne: null } }; // exclude site-level aggregate rows
if (SITE_UUID) {
filter.site_uuid = SITE_UUID;
}
// Get all agents from MongoDB summaries collection, deduplicated by agent_uuid
const agentSummaries = await db.collection('summaries')
.aggregate([
{ $match: filter },
{ $sort: { timestamp: -1 } },
{
$group: {
_id: '$agent_uuid',
lastSeen: { $first: '$timestamp' },
label: { $first: '$agent_label' },
site_uuid: { $first: '$site_uuid' }, // capture the site this agent belongs to
}
},
// Final guard: only keep agents whose primary site matches the queried site
...(SITE_UUID ? [{ $match: { site_uuid: SITE_UUID } }] : []),
]).toArray();
// Check for any flows recorded in the last 12 hours to determine online status
const twelveHoursAgo = new Date(Date.now() - 12 * 3600000);
const activeAgents = await db.collection('flows').distinct('agent_uuid', {
timestamp: { $gte: twelveHoursAgo }
});
const activeAgentsSet = new Set(activeAgents);
const agents: Agent[] = agentSummaries.map((item: any, idx: number) => {
const lastSeenDate = item.lastSeen ? new Date(item.lastSeen) : null;
const isOnline = activeAgentsSet.has(item._id);
const statusHuman = isOnline
? 'Online'
: lastSeenDate
? `Last Seen ${lastSeenDate.toLocaleString('id-ID', { timeZone: 'Asia/Jakarta', day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })} WIB`
: 'Offline';
return {
id: idx + 1,
uuid: item._id,
serial: item._id,
site_uuid: SITE_UUID || '',
organization_uuid: '',
provisioned: true,
activated: isOnline,
label: item.label || item._id,
created_at: { human: 'N/A', date: '', unix_time: 0 },
updated_at: { human: 'N/A', date: '', unix_time: 0 },
last_seen_at: {
human: statusHuman,
date: lastSeenDate?.toISOString() || '',
unix_time: lastSeenDate ? lastSeenDate.getTime() / 1000 : 0,
},
};
});
return agents;
}
const response = await fetch(API_URL, {
method: "GET",
headers: getHeaders(),
cache: "no-store",
});
if (!response.ok) {
if (response.status === 403) {
throw new Error("Asset Management Disabled: The configured API key does not have permission to manage provisioning agents (403 Forbidden).");
}
throw new Error(`Failed to fetch agents: ${response.statusText} (${response.status})`);
}
const result = await response.json();
if (result.status_code !== 0) {
throw new Error(result.status_message || "Unknown API error");
}
return result.data || [];
} catch (error: any) {
console.error("Error fetching agents:", error);
throw new Error(error.message || "Failed to retrieve agents inventory.");
}
}
export async function getAgent(agentId: string): Promise<Agent | null> {
try {
const API_KEY = getApiKey();
if (API_KEY && API_KEY.startsWith("sk_db_")) {
const agents = await getAgents();
return agents.find(a => a.uuid === agentId || String(a.id) === agentId) || null;
}
const response = await fetch(`${API_URL}/${agentId}`, {
method: "GET",
headers: getHeaders(),
cache: "no-store",
});
if (!response.ok) {
throw new Error(`Failed to fetch agent ${agentId}: ${response.statusText}`);
}
const result = await response.json();
return result.data;
} catch (error) {
console.error(`Error fetching agent ${agentId}:`, error);
return null;
}
}
export async function createAgent(agentId: string, label: string) {
try {
const API_KEY = getApiKey();
if (API_KEY && API_KEY.startsWith("sk_db_")) {
return { success: false, message: "Action denied: Site-scoped API Key is not allowed to create Agents." };
}
const url = agentId
? `${API_URL}/${encodeURIComponent(agentId)}?label=${encodeURIComponent(label)}`
: `${API_URL}?label=${encodeURIComponent(label)}`;
const response = await fetch(url, {
method: "POST",
headers: getHeaders(),
});
const result = await response.json();
if (!response.ok || result.status_code !== 0) {
return { success: false, message: result.status_message || response.statusText };
}
revalidatePath('/agents');
return { success: true, data: result.data };
} catch (error: any) {
return { success: false, message: error.message };
}
}
export async function updateAgent(agentId: string, label: string) {
try {
const API_KEY = getApiKey();
if (API_KEY && API_KEY.startsWith("sk_db_")) {
return { success: false, message: "Action denied: Site-scoped API Key is not allowed to modify Agents." };
}
const response = await fetch(`${API_URL}/${encodeURIComponent(agentId)}?label=${encodeURIComponent(label)}`, {
method: "PATCH",
headers: getHeaders(),
});
const result = await response.json();
if (!response.ok || result.status_code !== 0) {
return { success: false, message: result.status_message || response.statusText };
}
revalidatePath('/agents');
return { success: true, data: result.data };
} catch (error: any) {
return { success: false, message: error.message };
}
}
export async function deleteAgent(agentId: string) {
try {
const API_KEY = getApiKey();
if (API_KEY && API_KEY.startsWith("sk_db_")) {
return { success: false, message: "Action denied: Site-scoped API Key is not allowed to delete Agents." };
}
const response = await fetch(`${API_URL}/${encodeURIComponent(agentId)}`, {
method: "DELETE",
headers: getHeaders(),
});
const result = await response.json();
if (!response.ok || result.status_code !== 0) {
return { success: false, message: result.status_message || response.statusText };
}
revalidatePath('/agents');
return { success: true };
} catch (error: any) {
return { success: false, message: error.message };
}
}