7 Commits
9 changed files with 71 additions and 24 deletions

No files matched your search

+2 -2
View File
@@ -16,7 +16,7 @@ Produk BackOne oleh **PT. Data Bisnis Solusi** — Dashboard monitoring jaringan
│ │ Netify API → │ │ MongoDB │ │
│ │ MongoDB writer │ │ Database │ │
│ └──────────────────┘ └─────────────┘ │
│ Network: backone-infra │
│ Network: backone-infra │
└─────────────────────────────────────────┘
│ MongoDB shared
┌─────────────────────────────────────────┐
@@ -28,7 +28,7 @@ Produk BackOne oleh **PT. Data Bisnis Solusi** — Dashboard monitoring jaringan
│ │ REST API │──│ Next.js │ │
│ │ MongoDB reader │ │ Dashboard │ │
│ └──────────────────┘ └─────────────┘ │
│ Network: backone-app │
│ Network: backone-app │
└─────────────────────────────────────────┘
```
+36
View File
@@ -239,6 +239,27 @@ router.get('/agents/:uuid/subnets', async (req, res) => {
// ─── PUT /api/dashboard/agents/:uuid/subnets ─────────────────────────────────
// Simpan konfigurasi subnet yang diizinkan untuk agent tertentu
// Body: { allowed_subnets: ["192.168.1", "10.21"] }
function ipToLong(ip) {
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
}
function ipMatchesSubnets(ip, subnets) {
if (!subnets || subnets.length === 0) return true;
if (!ip) return false;
return subnets.some(subnet => {
if (subnet.includes('/')) {
try {
const [range, bitsStr] = subnet.split('/');
const bits = parseInt(bitsStr, 10);
if (isNaN(bits) || bits < 0 || bits > 32) return false;
const mask = bits === 0 ? 0 : (~0 << (32 - bits)) >>> 0;
return (ipToLong(ip) & mask) === (ipToLong(range) & mask);
} catch (e) { return false; }
} else { return ip === subnet; }
});
}
// PUT /api/dashboard/agents/:uuid/subnets
router.put('/agents/:uuid/subnets', async (req, res) => {
try {
const allowedRoles = ['SUPER_ADMIN', 'TENANT_ADMIN', 'COMPANY_ADMIN'];
@@ -251,6 +272,21 @@ router.put('/agents/:uuid/subnets', async (req, res) => {
{ uuid: req.params.uuid },
{ $set: { allowed_subnets: subnets, subnets_updated_at: new Date() } }
);
// Auto-cleanup background task
if (subnets.length > 0) {
setTimeout(async () => {
try {
const ips = await db.collection('devicestats').distinct('ip_address', { agent_uuid: req.params.uuid });
const invalidIps = ips.filter(ip => !ipMatchesSubnets(ip, subnets));
if (invalidIps.length > 0) {
await db.collection('devicestats').deleteMany({ agent_uuid: req.params.uuid, ip_address: { $in: invalidIps } });
await db.collection('flows').deleteMany({ agent_uuid: req.params.uuid, src_ip: { $in: invalidIps } });
}
} catch (e) { console.error('Auto-cleanup error:', e); }
}, 100);
}
res.json({ ok: true, data: { allowed_subnets: subnets } });
} catch (err) {
res.status(500).json({ ok: false, error: err.message });
+1 -1
View File
@@ -38,7 +38,7 @@ services:
environment:
- NODE_ENV=production
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
- INTERNAL_API_URL=${INTERNAL_API_URL:-http://backone-inspect-backend-bun-0:3001}
- INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:3001}
- JWT_SECRET=${JWT_SECRET:-super-secret-backone-key}
- MONGODB_URI=${MONGODB_URI}
- BACKONE_API_KEY=${NETIFY_API_KEY:-sk_db_source2}
+5 -1
View File
@@ -13,6 +13,10 @@ async function fetchFlows(limit = 500, agentUuid = null, siteUuid = null, interv
backoneFetch('/data/stats/top/tls_sni/download', { filter_interval: intervalMinutes, settings_limit: 50 }, agentUuid, siteUuid),
]);
if (!raw || !Array.isArray(raw)) return null;
// Safety check: Filter flows strictly to the requested agent to prevent Org-level pollution
const filteredRaw = agentUuid ? raw.filter(r => r.agent_uuid === agentUuid) : raw;
const sniList = [];
if (sniRaw && Array.isArray(sniRaw)) {
for (const r of sniRaw) {
@@ -22,7 +26,7 @@ async function fetchFlows(limit = 500, agentUuid = null, siteUuid = null, interv
}
}
}
return raw.map(r => {
return filteredRaw.map(r => {
const port = r.remote_port ?? null;
const portService = port ? (stats.PORT_SERVICE_MAP[port] ?? `Port ${port}`) : null;
const appLabel = r.application?.label || portService;
+2 -1
View File
@@ -37,7 +37,8 @@ function fixDates(obj) {
async function backoneFetch(endpoint, params = {}, agentUuid = null, siteUuid = null) {
if (agentUuid) {
const agentId = agentMap[agentUuid];
if (agentId) {
// Only use filter_agents if agentId is a number or numeric string
if (agentId && !isNaN(Number(agentId))) {
params.filter_agents = `[${agentId}]`;
} else {
params.settings_agent = agentUuid;
+1 -1
View File
@@ -103,7 +103,7 @@ export function getAgentColumns({
className: "w-[10%] text-center",
accessor: (row) => {
const uuid = row.uuid || row.serial;
const uptime = uptimeMap[uuid] ?? 100;
const uptime = uptimeMap[uuid] ?? 0;
let color = "text-emerald-400";
if (uptime < 90) color = "text-red-400";
else if (uptime < 98) color = "text-amber-400";
+2 -2
View File
@@ -99,8 +99,8 @@ export default function AgentsPage() {
return hum === 'Online' || hum === 'Flows Detected' || (hum && hum.startsWith('Last seen:'));
}).length;
const offlineAgents = totalAgents - onlineAgents;
const uptimeValues = agents.map(a => uptimeMap[a.uuid || a.serial] ?? 100);
const avgUptime = uptimeValues.length > 0 ? uptimeValues.reduce((s, v) => s + v, 0) / uptimeValues.length : 100;
const uptimeValues = agents.map(a => uptimeMap[a.uuid || a.serial] ?? 0);
const avgUptime = uptimeValues.length > 0 ? uptimeValues.reduce((s, v) => s + v, 0) / uptimeValues.length : 0;
// External Accounts di halaman Agents = akun eksternal teknikal/operasional saja
// AGENT_VIEWER = akun network agent (sudah ada di tabel agents) → BUKAN akun eksternal
+6
View File
@@ -35,6 +35,12 @@ export async function updateAgent(agentId: string, label: string) {
{ $set: { label: label } }
);
// Bismillah: Hapus semua data duplikat/hantu milik agent ini agar tidak ditimpa oleh proxy lawas/bug ganda
await db.collection('agent_registry').deleteMany({
$or: [{ uuid: agentId }, { serial: agentId }],
_id: { $ne: agent._id }
});
// Also try to update the summaries if possible, so historical displays match
await db.collection('summaries').updateMany(
{ agent_uuid: agent.uuid },
+16 -16
View File
@@ -89,7 +89,7 @@ export async function fetchAgentsFromMongo(
}
// 3. Optimize status check and last seen per agent using parallel indexed queries
const twentyFourHoursAgo = new Date(Date.now() - 24 * 3600000);
const fifteenMinutesAgoQuery = new Date(Date.now() - 15 * 60000);
const agentResults = await Promise.all(
agentSource.map(async (item: any, idx: number) => {
@@ -99,7 +99,7 @@ export async function fetchAgentsFromMongo(
// Execute status checks for active flow, latest flow, and latest summary in parallel
const [activeFlow, latestFlow, latestSummary] = await Promise.all([
db.collection('flows').findOne(
{ agent_uuid: uuid, timestamp: { $gte: twentyFourHoursAgo } },
{ agent_uuid: uuid, timestamp: { $gte: fifteenMinutesAgoQuery } },
{ projection: { _id: 1 } }
),
db.collection('flows').findOne(
@@ -112,8 +112,6 @@ export async function fetchAgentsFromMongo(
),
]);
const isOnline = !!activeFlow;
const registryLastSeenRaw = item.last_seen_at;
let lastSeenDate: Date | null = null;
if (registryLastSeenRaw?.date) {
@@ -137,19 +135,21 @@ export async function fetchAgentsFromMongo(
}
}
const isOnline = !!activeFlow;
let statusHuman = 'No Flows Detected';
if (isOnline) {
if (lastSeenDate) {
const now = new Date();
const isToday = lastSeenDate.getDate() === now.getDate() &&
lastSeenDate.getMonth() === now.getMonth() &&
lastSeenDate.getFullYear() === now.getFullYear();
const timeStr = lastSeenDate.toLocaleTimeString('id-ID', { timeZone: 'Asia/Jakarta', hour: '2-digit', minute: '2-digit' }) + ' WIB';
const dateStr = lastSeenDate.toLocaleDateString('id-ID', { timeZone: 'Asia/Jakarta', day: '2-digit', month: '2-digit' });
statusHuman = isToday ? `Last seen: ${timeStr}` : `Last seen: ${dateStr} ${timeStr}`;
} else {
statusHuman = 'Last seen: Just now';
}
if (lastSeenDate) {
const now = new Date();
const isToday = lastSeenDate.getDate() === now.getDate() &&
lastSeenDate.getMonth() === now.getMonth() &&
lastSeenDate.getFullYear() === now.getFullYear();
const timeStr = lastSeenDate.toLocaleTimeString('id-ID', { timeZone: 'Asia/Jakarta', hour: '2-digit', minute: '2-digit' }) + ' WIB';
const dateStr = lastSeenDate.toLocaleDateString('id-ID', { timeZone: 'Asia/Jakarta', day: '2-digit', month: '2-digit' });
const seenStr = isToday ? timeStr : `${dateStr} ${timeStr}`;
statusHuman = isOnline ? `Last seen: ${seenStr}` : `Offline (Last seen: ${seenStr})`;
} else if (isOnline) {
statusHuman = 'Last seen: Just now';
}
return {