style(sidebar): match active item highlight and header buttons 100% with demoplace.my.id

This commit is contained in:
rafif committed 2026-07-21 13:52:30 +07:00
1 parent 7f133a287b
commit 960322e30a
49 files changed
+3226 -1503

No files matched your search

+7 -2
View File
@@ -2,13 +2,18 @@ FROM oven/bun:1-alpine
WORKDIR /app
COPY proxy/package*.json ./
# Install dependencies first (layer caching)
# bun install is compatible with npm package.json / package-lock.json
COPY package*.json ./
RUN bun install --production
COPY proxy/ .
# Copy application source
COPY . .
# Expose proxy REST API port
EXPOSE 4000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
CMD bun -e "require('http').get('http://localhost:4000/health', r => r.statusCode === 200 ? process.exit(0) : process.exit(1)).on('error', () => process.exit(1))"
+34
View File
@@ -0,0 +1,34 @@
// check_device.js - Detailed check of 10.6.10.44 records
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
const mongoose = require('mongoose');
async function run() {
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/backone');
const db = mongoose.connection.db;
// Get all records for 10.6.10.44
const docs = await db.collection('devicestats')
.find({ ip_address: '10.6.10.44' })
.sort({ timestamp: 1 })
.toArray();
console.log(`Total docs for 10.6.10.44: ${docs.length}`);
docs.forEach((d, i) => {
console.log(`\n--- Doc ${i + 1} ---`);
console.log(' _id: ', d._id);
console.log(' agent_uuid: ', d.agent_uuid);
console.log(' timestamp: ', d.timestamp);
console.log(' created_at: ', d.created_at);
console.log(' updated_at: ', d.updated_at);
console.log(' download: ', d.download);
console.log(' device_label:', d.device_label);
});
// Check if there are different agent_uuids
const agents = [...new Set(docs.map(d => d.agent_uuid))];
console.log('\nDistinct agent_uuids for this IP:', agents);
await mongoose.disconnect();
}
run().catch(err => { console.error(err.message); process.exit(1); });
+73
View File
@@ -0,0 +1,73 @@
// proxy/clean_devicestat_duplicates.js
// ─────────────────────────────────────────────────────────────────────────────
// One-time cleanup script to deduplicate historical DeviceStat records.
// Keeps only the LATEST document per (agent_uuid, ip_address) pair,
// removing all older duplicates accumulated before the upsert fix.
//
// Usage: node proxy/clean_devicestat_duplicates.js
// ─────────────────────────────────────────────────────────────────────────────
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
const mongoose = require('mongoose');
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/backone';
const DeviceStatSchema = new mongoose.Schema({
timestamp: { type: Date },
agent_uuid: { type: String },
site_uuid: { type: String },
ip_address: { type: String },
mac_address: { type: String },
device_label: String,
device_type: String,
os_label: String,
manufacturer: String,
download: Number,
upload: Number,
flows: Number,
last_seen: String,
}, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } });
const DeviceStat = mongoose.model('DeviceStat', DeviceStatSchema);
async function run() {
console.log('[Cleanup] Connecting to MongoDB...');
await mongoose.connect(MONGODB_URI);
console.log('[Cleanup] Connected.');
// Find all unique (agent_uuid, ip_address) combinations
const groups = await DeviceStat.aggregate([
{ $group: {
_id: { agent_uuid: '$agent_uuid', ip_address: '$ip_address' },
ids: { $push: '$_id' },
timestamps: { $push: '$timestamp' },
count: { $sum: 1 },
}},
{ $match: { count: { $gt: 1 } } },
]);
console.log(`[Cleanup] Found ${groups.length} (agent_uuid, ip_address) pairs with duplicates.`);
let totalDeleted = 0;
for (const group of groups) {
// Sort the ids by matching timestamps - keep the latest
const paired = group.ids.map((id, i) => ({ id, ts: group.timestamps[i] }));
paired.sort((a, b) => new Date(b.ts) - new Date(a.ts));
// Keep the first (newest), delete the rest
const toDelete = paired.slice(1).map(p => p.id);
const result = await DeviceStat.deleteMany({ _id: { $in: toDelete } });
totalDeleted += result.deletedCount;
}
const remaining = await DeviceStat.countDocuments();
console.log(`[Cleanup] Done. Deleted ${totalDeleted} duplicate DeviceStat records.`);
console.log(`[Cleanup] Remaining DeviceStat documents: ${remaining}`);
await mongoose.disconnect();
}
run().catch(err => {
console.error('[Cleanup] Fatal error:', err.message);
process.exit(1);
});
+1 -42
View File
@@ -18,8 +18,6 @@ const {
} = require('./collectorHelperDpi2');
const { Summary, AppStat } = require('./models/Schemas');
const { LookupApp } = require('./models/SchemasAux');
const { BASE_URL } = require('./netifyClientCore');
const { pruneOldData } = require('./dataRetention');
const SITE_UUIDS_STR = process.env.NETIFY_SITE_UUIDS || process.env.NETIFY_SITE_UUID;
@@ -257,47 +255,8 @@ async function collectSpecificAgents(agentUuids, siteUuid = SITE_UUIDS[0], delay
return { success: true, mode: 'specific_agents', agents_count: agentUuids.length, successful, results };
}
async function populateLookupApps(siteUuid = '') {
try {
const axios = require('axios');
const headers = { 'Accept': 'application/json' };
const res = await axios.get(`${BASE_URL}/lookup/applications`, {
headers, params: { settings_limit: 10000 }, timeout: 20000
});
const apps = res.data?.data;
if (!apps || !Array.isArray(apps)) {
console.log('[LookupApp] No application data from DPI API');
return { success: false, count: 0 };
}
let upserted = 0;
for (const a of apps) {
if (!a.id) continue;
const doc = {
id: a.id,
tag: a.tag || '',
label: a.label || '',
name: a.full_label || a.label || '',
full_name: a.full_label || '',
description: a.description || '',
favicon: a.favicon || '',
icon: a.icon || '',
logo: a.logo || '',
application_category: a.category || null
};
await LookupApp.updateOne({ id: a.id }, { $set: doc }, { upsert: true });
upserted++;
}
console.log(`[LookupApp] Upserted ${upserted} applications`);
return { success: true, count: upserted };
} catch (err) {
console.error('[LookupApp] Population error:', err.message);
return { success: false, count: 0 };
}
}
module.exports = {
collectAllAgents,
collectSpecificAgent,
collectSpecificAgents,
populateLookupApps
collectSpecificAgents
};
+42 -6
View File
@@ -115,16 +115,34 @@ async function collectFlows(agentUuid, timestamp, SITE_UUID, netify, label, ipTo
const blacklistedCategories = new Set(blacklistRules.filter(r => r.type === 'category').map(r => r.value.toLowerCase()));
const blacklistedDomains = new Set(blacklistRules.filter(r => r.type === 'domain').map(r => r.value.toLowerCase()));
const flowIdsInBatch = flowDocs.map(f => f.flow_id).filter(Boolean);
const existingFlowThreats = new Set(
await Threat.find({ flow_id: { $in: flowIdsInBatch } }).distinct('flow_id')
);
const threatDocs = [];
const eventDocs = [];
for (const f of flowDocs) {
let isViolation = false;
let categoryLabel = "";
// Check if domain is blacklisted
if (f.domain && blacklistedDomains.has(f.domain.toLowerCase())) {
isViolation = true;
} else if (f.app_label && blacklistedDomains.has(f.app_label.toLowerCase())) {
isViolation = true;
for (const r of blacklistRules) {
if (r.type === 'domain') {
const val = r.value.toLowerCase();
// Direct domain match
if (f.domain && f.domain.toLowerCase().includes(val)) {
isViolation = true;
break;
}
// Main domain part match against app label (e.g. "google" from "google.com")
const mainDomainPart = val.split('.')[0];
if (mainDomainPart && f.app_label && f.app_label.toLowerCase().includes(mainDomainPart)) {
isViolation = true;
break;
}
}
}
// Look up app details to check category
@@ -138,7 +156,7 @@ async function collectFlows(agentUuid, timestamp, SITE_UUID, netify, label, ipTo
}
}
if (isViolation) {
if (isViolation && !existingFlowThreats.has(f.flow_id)) {
threatDocs.push({
timestamp,
agent_uuid: f.agent_uuid,
@@ -150,7 +168,21 @@ async function collectFlows(agentUuid, timestamp, SITE_UUID, netify, label, ipTo
dst_port: f.dst_port,
protocol: f.protocol,
description: `Access to blacklisted app/domain: ${f.app_label} (${f.domain || 'N/A'})${categoryLabel ? ' - Category: ' + categoryLabel : ''}`,
event_at: new Date().toISOString()
event_at: new Date().toISOString(),
flow_id: f.flow_id
});
eventDocs.push({
timestamp,
agent_uuid: f.agent_uuid,
site_uuid: f.site_uuid,
event_type: "blacklist_violation",
severity: "Warning",
description: `Access to blacklisted app/domain: ${f.app_label} (${f.domain || 'N/A'})${categoryLabel ? ' - Category: ' + categoryLabel : ''}`,
ip_address: f.src_ip,
mac_address: f.src_mac,
event_at: new Date(),
flow_id: f.flow_id
});
}
}
@@ -159,6 +191,10 @@ async function collectFlows(agentUuid, timestamp, SITE_UUID, netify, label, ipTo
await Threat.insertMany(threatDocs);
console.log(`[Collector] ✓ ${threatDocs.length} blacklist policy violation threats recorded for ${label}`);
}
if (eventDocs.length > 0) {
await Event.insertMany(eventDocs);
console.log(`[Collector] ✓ ${eventDocs.length} blacklist policy violation events recorded for ${label}`);
}
}
} catch (err) {
console.error('[Collector] Blacklist detection failed:', err.message);
+44
View File
@@ -0,0 +1,44 @@
// diagnostic.js - Run: node proxy/diagnostic.js
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
const mongoose = require('mongoose');
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/backone';
async function run() {
await mongoose.connect(MONGODB_URI);
const db = mongoose.connection.db;
// 1. Total count
const total = await db.collection('devicestats').countDocuments();
console.log('=== DeviceStat Total:', total);
// 2. Count for 10.6.10.44
const specific = await db.collection('devicestats').countDocuments({ ip_address: '10.6.10.44' });
console.log('=== Count for 10.6.10.44:', specific);
// 3. Sample doc for 10.6.10.44
const sample = await db.collection('devicestats').findOne({ ip_address: '10.6.10.44' });
console.log('=== Sample doc for 10.6.10.44:', JSON.stringify(sample, null, 2));
// 4. Duplicate groups (top 10)
const dups = await db.collection('devicestats').aggregate([
{ $group: { _id: { agent_uuid: '$agent_uuid', ip_address: '$ip_address' }, count: { $sum: 1 } } },
{ $match: { count: { $gt: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 10 }
]).toArray();
console.log('=== Top duplicate groups:', JSON.stringify(dups, null, 2));
// 5. Check what collection name is actually used
const collections = await db.listCollections().toArray();
console.log('=== Collections:', collections.map(c => c.name));
// 6. Check indexes on devicestats
const indexes = await db.collection('devicestats').indexes();
console.log('=== Indexes on devicestats:', JSON.stringify(indexes, null, 2));
await mongoose.disconnect();
}
run().catch(err => { console.error('ERROR:', err.message); process.exit(1); });
+79
View File
@@ -0,0 +1,79 @@
// fix_devicestat_index.js
// Creates a unique compound index on (agent_uuid, ip_address) in DeviceStat
// and deduplicates any remaining duplicates before creating the index.
// Run: node proxy/fix_devicestat_index.js
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
const mongoose = require('mongoose');
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/backone';
async function run() {
console.log('[Fix] Connecting to MongoDB...');
await mongoose.connect(MONGODB_URI);
const db = mongoose.connection.db;
const col = db.collection('devicestats');
// Step 1: Find all duplicates grouped by (agent_uuid, ip_address)
console.log('[Fix] Scanning for duplicates...');
const groups = await col.aggregate([
{
$group: {
_id: { agent_uuid: '$agent_uuid', ip_address: '$ip_address' },
ids: { $push: '$_id' },
timestamps: { $push: '$timestamp' },
count: { $sum: 1 },
}
},
{ $match: { count: { $gt: 1 } } },
]).toArray();
console.log(`[Fix] Found ${groups.length} duplicate groups.`);
let deleted = 0;
for (const group of groups) {
// Sort by timestamp descending — keep the newest
const paired = group.ids.map((id, i) => ({ id, ts: new Date(group.timestamps[i] || 0) }));
paired.sort((a, b) => b.ts - a.ts);
const toDelete = paired.slice(1).map(p => p.id);
const result = await col.deleteMany({ _id: { $in: toDelete } });
deleted += result.deletedCount;
}
console.log(`[Fix] Deleted ${deleted} duplicate documents.`);
const remaining = await col.countDocuments();
console.log(`[Fix] Remaining DeviceStat documents: ${remaining}`);
// Step 2: Drop old non-unique compound index if it exists
try {
await col.dropIndex('agent_uuid_1_timestamp_-1_ip_address_1');
console.log('[Fix] Dropped old compound index.');
} catch (e) {
console.log('[Fix] Old index not found or already dropped:', e.message);
}
// Step 3: Create UNIQUE compound index on (agent_uuid, ip_address)
try {
await col.createIndex(
{ agent_uuid: 1, ip_address: 1 },
{ unique: true, name: 'agent_uuid_1_ip_address_1_unique', background: true }
);
console.log('[Fix] Created unique index on (agent_uuid, ip_address).');
} catch (e) {
console.error('[Fix] Failed to create unique index:', e.message);
}
// Step 4: Verify indexes
const indexes = await col.indexes();
console.log('[Fix] Current indexes:');
indexes.forEach(idx => console.log(` - ${idx.name}: ${JSON.stringify(idx.key)} ${idx.unique ? '[UNIQUE]' : ''}`));
// Step 5: Verify 10.6.10.44
const cnt = await col.countDocuments({ ip_address: '10.6.10.44' });
console.log(`\n[Fix] Count for 10.6.10.44: ${cnt} (should be 1)`);
await mongoose.disconnect();
console.log('[Fix] Done.');
}
run().catch(err => { console.error('[Fix] Fatal:', err.message); process.exit(1); });
+2
View File
@@ -103,6 +103,7 @@ const ThreatSchema = new mongoose.Schema({
protocol: String,
description: String,
event_at: String,
flow_id: { type: String, index: true },
}, baseOptions);
// ─── App Categories (per agent) ───────────────────────────────────────────────
@@ -129,6 +130,7 @@ const EventSchema = new mongoose.Schema({
ip_address: String,
mac_address: String,
event_at: Date,
flow_id: { type: String, index: true },
}, baseOptions);
// ─── Compound indexes for common dashboard queries ─────────────────────────────
+1 -2
View File
@@ -170,8 +170,7 @@ async function fetchFlows(limit = 10000, agentUuid = null, siteUuid = null) {
const port = r.remote_port ?? null;
const portService = port ? (PORT_SERVICE_MAP[port] ?? `Port ${port}`) : null;
const appLabel = r.application?.label || portService;
const sniVal = typeof r.tls_sni === 'object' ? r.tls_sni?.label : r.tls_sni;
const domain = sniVal || r.dns_hostname || r.hostname || null;
const domain = r.tls_sni || r.dns_hostname || r.hostname || null;
return {
flow_id: String(r.flow_id ?? ''),
src_ip: r.local_ip?.address ?? null,
+2 -3
View File
@@ -111,14 +111,13 @@ async function fetchTopCountries(interval = 1440, limit = 100, agentUuid = null,
const ulMap = {};
if (ulData) {
for (const r of ulData) {
const cc = r.country?.code || r.country?.label;
const cc = r.country?.code;
if (cc) ulMap[cc] = r.upload ?? 0;
}
}
return dlData.map(r => {
const cc = r.country?.code || r.country?.label;
return {
country_code: cc || 'Unknown',
country_code: r.country?.code ?? 'Unknown',
country_name: r.country?.label ?? '',
download: r.download ?? 0,
upload: ulMap[r.country?.code] ?? 0,
+3 -12
View File
@@ -3,7 +3,7 @@
// Runs every 5 minutes, collecting data for all agents or a specific agent.
const cron = require('node-cron');
const { collectAllAgents, collectSpecificAgent, collectSpecificAgents, populateLookupApps } = require('./collector');
const { collectAllAgents, collectSpecificAgent, collectSpecificAgents } = require('./collector');
// Mode: 'all' = collect all agents, 'agent' = collect one specific agent, 'agents' = collect multiple agents
const COLLECT_MODE = process.env.PROXY_COLLECT_MODE || 'all';
@@ -21,7 +21,6 @@ let lastRunAt = null;
let lastRunResult = null;
let runCount = 0;
let lastCapacityLogAt = 0;
let lastAppLookupRefresh = 0;
/**
* Execute one collection cycle (called by cron and manual trigger).
@@ -59,14 +58,6 @@ async function runCollection() {
const { logCapacityStats } = require('./db/capacityTracker');
await logCapacityStats(`[PROXY] [MongoDB] Capacity Used after Run #${runCount}:`);
}
// Refresh application lookup catalog daily (expensive: fetches 5000+ apps)
if (now - (lastAppLookupRefresh || 0) >= CAPACITY_LOG_INTERVAL_MS) {
lastAppLookupRefresh = now;
await populateLookupApps('').catch(err =>
console.error('[Scheduler] LookupApp refresh failed:', err.message)
);
}
return lastRunResult;
} catch (err) {
@@ -105,8 +96,8 @@ function startScheduler() {
// Initial run immediately on startup (async, do not block server start)
setTimeout(async () => {
// 1. Sync application lookup catalog from DPI API
populateLookupApps('').catch(err => console.error('[Scheduler] Initial LookupApp sync failed:', err.message));
// 1. Sync dictionary first
// await netifyClient.syncApplicationDictionary();
// 2. Start normal telemetry collection
runCollection().catch(err => console.error('[Scheduler] Initial run error:', err.message));
+71
View File
@@ -0,0 +1,71 @@
// test_api.js - Tests the actual metadata-detail API endpoint
// Run: node proxy/test_api.js
const http = require('http');
function request(path) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'localhost',
port: 3001,
path,
method: 'GET',
};
const req = http.request(options, res => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try { resolve({ status: res.statusCode, data: JSON.parse(body) }); }
catch (e) { resolve({ status: res.statusCode, raw: body }); }
});
});
req.on('error', reject);
req.end();
});
}
async function main() {
// Test 1: netbios_hostname for 10.6.10.44
console.log('\n=== TEST 1: netbios_hostname=10.6.10.44 ===');
try {
const r1 = await request('/api/dashboard/metadata-detail?type=netbios_hostname&value=10.6.10.44');
console.log('Status:', r1.status);
if (r1.data) {
console.log('Count:', r1.data.count);
console.log('First 3 rows:', JSON.stringify(r1.data.data?.slice(0, 3), null, 2));
} else {
console.log('Raw:', r1.raw?.slice(0, 500));
}
} catch (e) {
console.log('ERROR (maybe backend is on different port):', e.message);
}
// Test 2: Try port 3000 (Next.js API routes)
console.log('\n=== TEST 2: via Next.js port 3000 ===');
try {
const r2 = await request('/api/dashboard/metadata-detail?type=netbios_hostname&value=10.6.10.44');
const options2 = { hostname: 'localhost', port: 3000, path: '/api/dashboard/metadata-detail?type=netbios_hostname&value=10.6.10.44', method: 'GET' };
const r3 = await new Promise((resolve, reject) => {
const req = http.request(options2, res => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try { resolve({ status: res.statusCode, data: JSON.parse(body) }); }
catch (e) { resolve({ status: res.statusCode, raw: body?.slice(0, 500) }); }
});
});
req.on('error', reject);
req.end();
});
console.log('Port 3000 - Status:', r3.status);
if (r3.data) {
console.log('Count:', r3.data.count);
console.log('First 3 rows:', JSON.stringify(r3.data.data?.slice(0, 3), null, 2));
} else {
console.log('Raw:', r3.raw);
}
} catch (e) {
console.log('Port 3000 ERROR:', e.message);
}
}
main().catch(console.error);
+1
View File
@@ -0,0 +1 @@
const mongoose = require('mongoose'); require('dotenv').config({ path: '../.env.local' }); const { LookupApp } = require('./models/Schemas'); async function test() { await mongoose.connect(process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/backone_dpi'); const sample = await LookupApp.findOne({ tag: /youtube/i }).lean(); console.log(JSON.stringify(sample, null, 2)); await mongoose.disconnect(); } test().catch(console.error);
+89
View File
@@ -0,0 +1,89 @@
// test_metadata_detail.js - Test after restart
// Run: node proxy/test_metadata_detail.js
const http = require('http');
function post(path, body) {
return new Promise((resolve, reject) => {
const data = JSON.stringify(body);
const options = {
hostname: 'localhost', port: 3001, path, method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
};
const req = http.request(options, res => {
let buf = '';
res.on('data', c => buf += c);
res.on('end', () => {
try { resolve({ status: res.statusCode, headers: res.headers, data: JSON.parse(buf) }); }
catch { resolve({ status: res.statusCode, raw: buf }); }
});
});
req.on('error', reject);
req.write(data);
req.end();
});
}
function get(path, cookie) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'localhost', port: 3001, path, method: 'GET',
headers: cookie ? { Cookie: cookie } : {},
};
const req = http.request(options, res => {
let buf = '';
res.on('data', c => buf += c);
res.on('end', () => {
try { resolve({ status: res.statusCode, data: JSON.parse(buf) }); }
catch { resolve({ status: res.statusCode, raw: buf?.slice(0, 300) }); }
});
});
req.on('error', reject);
req.end();
});
}
async function main() {
// Step 1: Login to get cookie
console.log('=== Step 1: Login ===');
const login = await post('/api/auth/login', { username: 'admin', password: 'admin123' });
console.log('Login status:', login.status);
const setCookie = login.headers?.['set-cookie'];
let cookie = '';
if (setCookie) {
cookie = setCookie.map(c => c.split(';')[0]).join('; ');
console.log('Cookie obtained:', cookie.slice(0, 60) + '...');
} else {
console.log('No cookie received. Auth response:', JSON.stringify(login.data));
// Try with a known admin credential
}
// Step 2: Test health
console.log('\n=== Step 2: Health Check ===');
const health = await get('/api/health', cookie);
console.log('Health:', health.status, JSON.stringify(health.data));
// Step 3: Test metadata-detail netbios_hostname
console.log('\n=== Step 3: metadata-detail netbios_hostname=10.6.10.44 ===');
const r = await get('/api/dashboard/metadata-detail?type=netbios_hostname&value=10.6.10.44', cookie);
console.log('Status:', r.status);
if (r.data) {
console.log('Count (should be 1):', r.data.count);
console.log('Data:', JSON.stringify(r.data.data, null, 2));
} else {
console.log('Raw:', r.raw);
}
// Step 4: Verify DB has 1 doc for 10.6.10.44
console.log('\n=== Step 4: Direct DB verification ===');
const mongoose = require('mongoose');
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/backone');
const db = mongoose.connection.db;
const cnt = await db.collection('devicestats').countDocuments({ ip_address: '10.6.10.44' });
console.log('DB count for 10.6.10.44:', cnt, '(expected: 1)');
await mongoose.disconnect();
}
main().catch(console.error);
+1
View File
@@ -0,0 +1 @@
const mongoose = require('mongoose'); require('dotenv').config({ path: '../.env.local' }); const { syncApplicationDictionary } = require('./netifyClient'); const { LookupApp } = require('./models/Schemas'); async function test() { await mongoose.connect(process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/backone_dpi'); console.log('Connected to DB'); await syncApplicationDictionary(); const count = await LookupApp.countDocuments(); console.log('Total LookupApps in DB:', count); await mongoose.disconnect(); } test().catch(console.error);
+56
View File
@@ -0,0 +1,56 @@
// verify_fix.js - Directly verify the MongoDB aggregation returns correct results
// This simulates what the backend metadata-detail endpoint does after the fix.
// Run: node proxy/verify_fix.js
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
const mongoose = require('mongoose');
async function run() {
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/backone');
const db = mongoose.connection.db;
const col = db.collection('devicestats');
const testValues = ['10.6.10.44'];
// Also find other common device_labels to test
const sample = await col.find({}).limit(20).toArray();
const labels = [...new Set(sample.map(d => d.device_label).filter(Boolean))];
console.log('Sample device_labels to test:', labels.slice(0, 5));
for (const value of [...testValues, ...labels.slice(0, 3)]) {
// Count raw docs matching
const rawCount = await col.countDocuments({ device_label: value });
// Simulate the new aggregation pipeline
const aggResult = await col.aggregate([
{ $match: { device_label: value } },
{ $sort: { timestamp: -1 } },
{ $group: {
_id: '$ip_address',
mac_address: { $first: '$mac_address' },
device_label: { $first: '$device_label' },
download: { $max: '$download' },
upload: { $max: '$upload' },
}},
{ $sort: { download: -1 } },
]).toArray();
const status = rawCount > aggResult.length ? '✅ FIXED (was duplicated)' : '✓ OK';
console.log(`\ndevice_label="${value}": raw=${rawCount} rows → aggregated=${aggResult.length} unique devices ${status}`);
if (aggResult.length > 0) {
console.log(' First result:', JSON.stringify(aggResult[0], null, 2));
}
}
// Verify unique index exists
const indexes = await col.indexes();
const uniqueIdx = indexes.find(i => i.unique && i.key.agent_uuid && i.key.ip_address);
console.log('\n=== Unique Index on (agent_uuid, ip_address):', uniqueIdx ? `✅ EXISTS (${uniqueIdx.name})` : '❌ MISSING');
// Final count
const total = await col.countDocuments();
console.log('=== Total DeviceStat docs:', total);
await mongoose.disconnect();
}
run().catch(err => { console.error(err.message); process.exit(1); });