feat(prod): deploy web dashboard, migrate mongodb configuration, resolve server components render error and fix logo static asset paths
This commit is contained in:
1 parent
4882108068
commit
b2ea883601
119 files changed
+7123
-2011
No files matched your search
+14
-148
@@ -55,43 +55,15 @@ app.use('/api/auth', authRoutes);
|
||||
app.use('/api/uploads', express.static(getUploadsDir()));
|
||||
|
||||
// ─── Auth Middleware ──────────────────────────────────────────────────────────
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'super-secret-backone-key';
|
||||
|
||||
function requireAuth(req, res, next) {
|
||||
const token = req.cookies?.token;
|
||||
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
||||
|
||||
try {
|
||||
req.user = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
// ── VIEW-AS MODE ──────────────────────────────────────────────────────────
|
||||
// Jika SUPER_ADMIN sedang dalam mode "View As Agent", frontend mengirim
|
||||
// header X-View-As-Agent berisi JWT token yang berisi agent_uuid yang dipilih.
|
||||
const viewAsHeader = req.headers['x-view-as-agent'];
|
||||
if (viewAsHeader && req.user.role === 'SUPER_ADMIN') {
|
||||
try {
|
||||
const viewDecoded = jwt.verify(viewAsHeader, JWT_SECRET);
|
||||
if (viewDecoded.type === 'view-as' && viewDecoded.adminId === req.user.id && viewDecoded.viewAs) {
|
||||
req.user = {
|
||||
...req.user,
|
||||
role: 'AGENT_VIEWER',
|
||||
agent_uuid: viewDecoded.viewAs,
|
||||
agent_label: viewDecoded.viewAsLabel,
|
||||
_viewAsMode: true,
|
||||
_originalRole: 'SUPER_ADMIN',
|
||||
};
|
||||
}
|
||||
} catch (viewErr) {
|
||||
console.warn('[ViewAs] Invalid view-as token, ignoring:', viewErr.message);
|
||||
}
|
||||
}
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
}
|
||||
const { requireAuth } = require('./middleware/auth');
|
||||
const { getTimeFilter, getBaseFilter } = require('./routes/dashboard/helpers');
|
||||
const {
|
||||
generateMacFromIp,
|
||||
resolveVendorFromIp,
|
||||
resolveDeviceTypeFromIp,
|
||||
resolveOSFromIp,
|
||||
generateAutoLabel
|
||||
} = require('./deviceResolver');
|
||||
|
||||
// ─── Protected Dashboard Routes ───────────────────────────────────────────────
|
||||
const dashboardRoutes = require('./routes/dashboard');
|
||||
@@ -99,109 +71,16 @@ const dashboardRoutes = require('./routes/dashboard');
|
||||
// Override /api/dashboard/app-details to show real-time device mapping per application
|
||||
app.get('/api/dashboard/app-details', requireAuth, (req, res) => {
|
||||
require('./routes/appDetailsHandler')(req, res, {
|
||||
getTimeFilter: (req) => {
|
||||
const range = req.query.timeRange || 'all';
|
||||
if (range === 'all') return null;
|
||||
const now = new Date();
|
||||
const ms = {
|
||||
'5m': 5 * 60000,
|
||||
'10m': 10 * 60000,
|
||||
'30m': 30 * 60000,
|
||||
'1h': 60 * 60000,
|
||||
'1d': 24 * 3600000,
|
||||
'7d': 7 * 24 * 3600000,
|
||||
};
|
||||
const delta = ms[range] ?? ms['1h'];
|
||||
return { $gte: new Date(now.getTime() - delta) };
|
||||
},
|
||||
getBaseFilter: (req, timeFilter = null) => {
|
||||
const filter = {};
|
||||
if (timeFilter) filter.timestamp = timeFilter;
|
||||
if (req.user?.site_uuid) filter.site_uuid = req.user.site_uuid;
|
||||
|
||||
// Agent-based isolation (RBAC / Multi-Tenant)
|
||||
if (req.user?.role === 'AGENT_VIEWER' && req.user?.agent_uuid) {
|
||||
filter.agent_uuid = req.user.agent_uuid;
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
getTimeFilter,
|
||||
getBaseFilter
|
||||
});
|
||||
});
|
||||
|
||||
// Override /api/dashboard/device-details to map real-time classifications (Facebook, YouTube, etc.)
|
||||
app.get('/api/dashboard/device-details', requireAuth, (req, res) => {
|
||||
const generateMacFromIp = (ip) => {
|
||||
if (!ip) return '00:16:3e:00:11:22';
|
||||
let hash = 0;
|
||||
for (let i = 0; i < ip.length; i++) {
|
||||
hash = (hash << 5) - hash + ip.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
const hex = Math.abs(hash).toString(16).padEnd(8, 'a');
|
||||
return `00:16:3e:${hex.substring(0,2)}:${hex.substring(2,4)}:${hex.substring(4,6)}`;
|
||||
};
|
||||
|
||||
const resolveVendorFromIp = (ip) => {
|
||||
if (!ip) return 'Intel Corporation';
|
||||
if (ip.startsWith('10.6.30.') || ip.startsWith('10.250.')) return 'Supermicro / Dell Inc.';
|
||||
if (ip.startsWith('10.6.10.') || ip.startsWith('10.6.11.')) return 'Cisco Systems, Inc.';
|
||||
if (ip.startsWith('192.168.')) return 'TP-Link Corporation';
|
||||
let hash = 0;
|
||||
for (let i = 0; i < ip.length; i++) hash = (hash << 5) - hash + ip.charCodeAt(i);
|
||||
const vendors = ['Intel Corporation', 'Asustek Computer Inc.', 'Apple Inc.', 'Hewlett Packard', 'Samsung Electronics'];
|
||||
return vendors[Math.abs(hash) % vendors.length];
|
||||
};
|
||||
|
||||
const resolveDeviceTypeFromIp = (ip) => {
|
||||
if (!ip) return 'Workstation';
|
||||
if (ip.endsWith('.1') || ip.endsWith('.254')) return 'Gateway / Router';
|
||||
if (ip.startsWith('10.6.30.')) return 'Database Server';
|
||||
if (ip.startsWith('10.250.')) return 'Core Network Node';
|
||||
if (ip.startsWith('10.6.12.')) return 'Finance Workstation';
|
||||
return 'Workstation / Laptop';
|
||||
};
|
||||
|
||||
const resolveOSFromIp = (ip) => {
|
||||
if (!ip) return 'Windows 11';
|
||||
if (ip.startsWith('10.6.30.') || ip.startsWith('10.250.')) return 'Linux (Ubuntu Server 24.04)';
|
||||
if (ip.startsWith('10.6.12.')) return 'Windows 11 Enterprise';
|
||||
if (ip.startsWith('192.168.')) return 'iOS / Android';
|
||||
return 'Windows 11 Pro';
|
||||
};
|
||||
|
||||
const generateAutoLabel = (ip, mac, manufacturer, deviceType) => {
|
||||
const brand = manufacturer && manufacturer !== '-' && manufacturer !== 'Unknown' ? manufacturer.split(' ')[0] : '';
|
||||
const type = deviceType && deviceType !== '-' && deviceType !== 'Unknown' ? deviceType : 'Device';
|
||||
const suffix = ip ? ip.split('.').slice(-2).join('.') : (mac ? mac.split(':').slice(-2).join(':') : 'Node');
|
||||
return brand ? `${brand} ${type} (${suffix})` : `${type} (${suffix})`;
|
||||
};
|
||||
|
||||
require('./routes/deviceDetailsHandler')(req, res, {
|
||||
// Device detail: default timeRange is 'all' so ALL historical data shows
|
||||
// Only respect explicit time filters if user deliberately passes one
|
||||
getTimeFilter: (req) => {
|
||||
const range = req.query.timeRange || 'all';
|
||||
if (range === 'all') return null;
|
||||
const now = new Date();
|
||||
const ms = {
|
||||
'5m': 5 * 60000,
|
||||
'30m': 30 * 60000,
|
||||
'1h': 60 * 60000,
|
||||
'1d': 24 * 3600000,
|
||||
'7d': 7 * 24 * 3600000,
|
||||
};
|
||||
const delta = ms[range] ?? ms['1h'];
|
||||
return { $gte: new Date(now.getTime() - delta) };
|
||||
},
|
||||
getBaseFilter: (req, timeFilter = null) => {
|
||||
const filter = {};
|
||||
if (timeFilter) filter.timestamp = timeFilter;
|
||||
if (req.user?.site_uuid) filter.site_uuid = req.user.site_uuid;
|
||||
if (req.user?.role === 'AGENT_VIEWER' && req.user?.agent_uuid) {
|
||||
filter.agent_uuid = req.user.agent_uuid;
|
||||
}
|
||||
return filter;
|
||||
},
|
||||
getTimeFilter,
|
||||
getBaseFilter,
|
||||
generateMacFromIp,
|
||||
resolveDeviceTypeFromIp,
|
||||
resolveOSFromIp,
|
||||
@@ -212,20 +91,7 @@ app.get('/api/dashboard/device-details', requireAuth, (req, res) => {
|
||||
|
||||
app.get('/api/dashboard/remote-ip-details', requireAuth, async (req, res) => {
|
||||
require('./routes/remoteIpDetailsHandler')(req, res, {
|
||||
getTimeFilter: (req) => {
|
||||
const range = req.query.timeRange || 'all';
|
||||
if (range === 'all') return null;
|
||||
const now = new Date();
|
||||
const ms = {
|
||||
'5m': 5 * 60000,
|
||||
'30m': 30 * 60000,
|
||||
'1h': 60 * 60000,
|
||||
'1d': 24 * 3600000,
|
||||
'7d': 7 * 24 * 3600000,
|
||||
};
|
||||
const delta = ms[range] ?? ms['1h'];
|
||||
return { $gte: new Date(now.getTime() - delta) };
|
||||
}
|
||||
getTimeFilter
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user