style(sidebar): match active item highlight and header buttons 100% with demoplace.my.id
This commit is contained in:
1 parent
7f133a287b
commit
960322e30a
49 files changed
+3226
-1503
No files matched your search
@@ -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))"
|
||||
|
||||
|
||||
@@ -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); });
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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); });
|
||||
@@ -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); });
|
||||
@@ -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 ─────────────────────────────
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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); });
|
||||
Reference in new issue
Block a user