Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b59d888abb | ||
|
|
0dcedf5d76 | ||
|
|
068435ffb9 | ||
|
|
f7ebd7a5c5 | ||
|
|
6a23577b08 | ||
|
|
f0d9a55a56 | ||
|
|
64f77187d8 | ||
|
|
8a73d266d8 | ||
|
|
bc30a8a0e4 |
No files matched your search
@@ -2,10 +2,10 @@ FROM oven/bun:1-alpine
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY backend/package*.json ./
|
COPY package*.json ./
|
||||||
RUN bun install --production
|
RUN bun install --production
|
||||||
|
|
||||||
COPY backend/ .
|
COPY . .
|
||||||
|
|
||||||
EXPOSE 3001
|
EXPOSE 3001
|
||||||
|
|
||||||
|
|||||||
@@ -239,6 +239,27 @@ router.get('/agents/:uuid/subnets', async (req, res) => {
|
|||||||
// ─── PUT /api/dashboard/agents/:uuid/subnets ─────────────────────────────────
|
// ─── PUT /api/dashboard/agents/:uuid/subnets ─────────────────────────────────
|
||||||
// Simpan konfigurasi subnet yang diizinkan untuk agent tertentu
|
// Simpan konfigurasi subnet yang diizinkan untuk agent tertentu
|
||||||
// Body: { allowed_subnets: ["192.168.1", "10.21"] }
|
// 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) => {
|
router.put('/agents/:uuid/subnets', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const allowedRoles = ['SUPER_ADMIN', 'TENANT_ADMIN', 'COMPANY_ADMIN'];
|
const allowedRoles = ['SUPER_ADMIN', 'TENANT_ADMIN', 'COMPANY_ADMIN'];
|
||||||
@@ -251,6 +272,21 @@ router.put('/agents/:uuid/subnets', async (req, res) => {
|
|||||||
{ uuid: req.params.uuid },
|
{ uuid: req.params.uuid },
|
||||||
{ $set: { allowed_subnets: subnets, subnets_updated_at: new Date() } }
|
{ $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 } });
|
res.json({ ok: true, data: { allowed_subnets: subnets } });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ ok: false, error: err.message });
|
res.status(500).json({ ok: false, error: err.message });
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
- 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}
|
- JWT_SECRET=${JWT_SECRET:-super-secret-backone-key}
|
||||||
- MONGODB_URI=${MONGODB_URI}
|
- MONGODB_URI=${MONGODB_URI}
|
||||||
- BACKONE_API_KEY=${NETIFY_API_KEY:-sk_db_source2}
|
- BACKONE_API_KEY=${NETIFY_API_KEY:-sk_db_source2}
|
||||||
|
|||||||
@@ -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),
|
backoneFetch('/data/stats/top/tls_sni/download', { filter_interval: intervalMinutes, settings_limit: 50 }, agentUuid, siteUuid),
|
||||||
]);
|
]);
|
||||||
if (!raw || !Array.isArray(raw)) return null;
|
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 = [];
|
const sniList = [];
|
||||||
if (sniRaw && Array.isArray(sniRaw)) {
|
if (sniRaw && Array.isArray(sniRaw)) {
|
||||||
for (const r of 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 port = r.remote_port ?? null;
|
||||||
const portService = port ? (stats.PORT_SERVICE_MAP[port] ?? `Port ${port}`) : null;
|
const portService = port ? (stats.PORT_SERVICE_MAP[port] ?? `Port ${port}`) : null;
|
||||||
const appLabel = r.application?.label || portService;
|
const appLabel = r.application?.label || portService;
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ function fixDates(obj) {
|
|||||||
async function backoneFetch(endpoint, params = {}, agentUuid = null, siteUuid = null) {
|
async function backoneFetch(endpoint, params = {}, agentUuid = null, siteUuid = null) {
|
||||||
if (agentUuid) {
|
if (agentUuid) {
|
||||||
const agentId = agentMap[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}]`;
|
params.filter_agents = `[${agentId}]`;
|
||||||
} else {
|
} else {
|
||||||
params.settings_agent = agentUuid;
|
params.settings_agent = agentUuid;
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export function getAgentColumns({
|
|||||||
className: "w-[10%] text-center",
|
className: "w-[10%] text-center",
|
||||||
accessor: (row) => {
|
accessor: (row) => {
|
||||||
const uuid = row.uuid || row.serial;
|
const uuid = row.uuid || row.serial;
|
||||||
const uptime = uptimeMap[uuid] ?? 100;
|
const uptime = uptimeMap[uuid] ?? 0;
|
||||||
let color = "text-emerald-400";
|
let color = "text-emerald-400";
|
||||||
if (uptime < 90) color = "text-red-400";
|
if (uptime < 90) color = "text-red-400";
|
||||||
else if (uptime < 98) color = "text-amber-400";
|
else if (uptime < 98) color = "text-amber-400";
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ export default function AgentsPage() {
|
|||||||
return hum === 'Online' || hum === 'Flows Detected' || (hum && hum.startsWith('Last seen:'));
|
return hum === 'Online' || hum === 'Flows Detected' || (hum && hum.startsWith('Last seen:'));
|
||||||
}).length;
|
}).length;
|
||||||
const offlineAgents = totalAgents - onlineAgents;
|
const offlineAgents = totalAgents - onlineAgents;
|
||||||
const uptimeValues = agents.map(a => uptimeMap[a.uuid || a.serial] ?? 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 : 100;
|
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
|
// External Accounts di halaman Agents = akun eksternal teknikal/operasional saja
|
||||||
// AGENT_VIEWER = akun network agent (sudah ada di tabel agents) → BUKAN akun eksternal
|
// AGENT_VIEWER = akun network agent (sudah ada di tabel agents) → BUKAN akun eksternal
|
||||||
@@ -175,11 +175,10 @@ export default function AgentsPage() {
|
|||||||
.filter(loc => agents.some(a => (a.uuid || a.serial) === loc.agent_uuid))
|
.filter(loc => agents.some(a => (a.uuid || a.serial) === loc.agent_uuid))
|
||||||
.map(loc => {
|
.map(loc => {
|
||||||
const agent = agents.find(a => (a.uuid || a.serial) === loc.agent_uuid);
|
const agent = agents.find(a => (a.uuid || a.serial) === loc.agent_uuid);
|
||||||
|
const dynamicLabel = resolveAgentLabel(loc.agent_uuid, managedUsers);
|
||||||
const agentLabel = agent
|
const agentLabel = agent
|
||||||
? (resolveAgentLabel(loc.agent_uuid, managedUsers) !== loc.agent_uuid
|
? (agent.label || (dynamicLabel !== loc.agent_uuid ? dynamicLabel : loc.agent_uuid))
|
||||||
? resolveAgentLabel(loc.agent_uuid, managedUsers)
|
: (loc.label || (dynamicLabel !== loc.agent_uuid ? dynamicLabel : loc.agent_uuid));
|
||||||
: (agent.label || loc.label || loc.agent_uuid))
|
|
||||||
: (loc.label || loc.agent_uuid);
|
|
||||||
const isOnline = !!(agent?.last_seen_at?.human === 'Online' ||
|
const isOnline = !!(agent?.last_seen_at?.human === 'Online' ||
|
||||||
agent?.last_seen_at?.human === 'Flows Detected' ||
|
agent?.last_seen_at?.human === 'Flows Detected' ||
|
||||||
(agent?.last_seen_at?.human && agent.last_seen_at.human.startsWith('Last seen:')));
|
(agent?.last_seen_at?.human && agent.last_seen_at.human.startsWith('Last seen:')));
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ export async function updateAgent(agentId: string, label: string) {
|
|||||||
{ $set: { label: label } }
|
{ $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
|
// Also try to update the summaries if possible, so historical displays match
|
||||||
await db.collection('summaries').updateMany(
|
await db.collection('summaries').updateMany(
|
||||||
{ agent_uuid: agent.uuid },
|
{ agent_uuid: agent.uuid },
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export async function fetchAgentsFromMongo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Optimize status check and last seen per agent using parallel indexed queries
|
// 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(
|
const agentResults = await Promise.all(
|
||||||
agentSource.map(async (item: any, idx: number) => {
|
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
|
// Execute status checks for active flow, latest flow, and latest summary in parallel
|
||||||
const [activeFlow, latestFlow, latestSummary] = await Promise.all([
|
const [activeFlow, latestFlow, latestSummary] = await Promise.all([
|
||||||
db.collection('flows').findOne(
|
db.collection('flows').findOne(
|
||||||
{ agent_uuid: uuid, timestamp: { $gte: twentyFourHoursAgo } },
|
{ agent_uuid: uuid, timestamp: { $gte: fifteenMinutesAgoQuery } },
|
||||||
{ projection: { _id: 1 } }
|
{ projection: { _id: 1 } }
|
||||||
),
|
),
|
||||||
db.collection('flows').findOne(
|
db.collection('flows').findOne(
|
||||||
@@ -112,8 +112,6 @@ export async function fetchAgentsFromMongo(
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isOnline = !!activeFlow;
|
|
||||||
|
|
||||||
const registryLastSeenRaw = item.last_seen_at;
|
const registryLastSeenRaw = item.last_seen_at;
|
||||||
let lastSeenDate: Date | null = null;
|
let lastSeenDate: Date | null = null;
|
||||||
if (registryLastSeenRaw?.date) {
|
if (registryLastSeenRaw?.date) {
|
||||||
@@ -137,8 +135,9 @@ export async function fetchAgentsFromMongo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isOnline = !!activeFlow;
|
||||||
|
|
||||||
let statusHuman = 'No Flows Detected';
|
let statusHuman = 'No Flows Detected';
|
||||||
if (isOnline) {
|
|
||||||
if (lastSeenDate) {
|
if (lastSeenDate) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const isToday = lastSeenDate.getDate() === now.getDate() &&
|
const isToday = lastSeenDate.getDate() === now.getDate() &&
|
||||||
@@ -146,11 +145,12 @@ export async function fetchAgentsFromMongo(
|
|||||||
lastSeenDate.getFullYear() === now.getFullYear();
|
lastSeenDate.getFullYear() === now.getFullYear();
|
||||||
const timeStr = lastSeenDate.toLocaleTimeString('id-ID', { timeZone: 'Asia/Jakarta', hour: '2-digit', minute: '2-digit' }) + ' WIB';
|
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 dateStr = lastSeenDate.toLocaleDateString('id-ID', { timeZone: 'Asia/Jakarta', day: '2-digit', month: '2-digit' });
|
||||||
statusHuman = isToday ? `Last seen: ${timeStr}` : `Last seen: ${dateStr} ${timeStr}`;
|
const seenStr = isToday ? timeStr : `${dateStr} ${timeStr}`;
|
||||||
} else {
|
|
||||||
|
statusHuman = isOnline ? `Last seen: ${seenStr}` : `Offline (Last seen: ${seenStr})`;
|
||||||
|
} else if (isOnline) {
|
||||||
statusHuman = 'Last seen: Just now';
|
statusHuman = 'Last seen: Just now';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: idx + 1,
|
id: idx + 1,
|
||||||
|
|||||||
Reference in new issue
Block a user