feat: complete multi-user RBAC integration, login system, and fix backend API crashing
This commit is contained in:
1 parent
4aa12511e8
commit
72ded25e4d
31 files changed
+954
-411
No files matched your search
@@ -0,0 +1,13 @@
|
|||||||
|
# Brand Guidelines: BackOne (PT. Data Bisnis Solusi)
|
||||||
|
- **Brand Identity**: The web dashboard is officially branded as "BackOne", a product by PT. Data Bisnis Solusi.
|
||||||
|
- **Logo**: Always use the BackOne logo (circular with a red-to-blue gradient and white curved shape). Reference it from `public/backone-logo.png`.
|
||||||
|
- **Color Palette**: Utilize the brand's core colors—vibrant Red and deep Blue (often as a gradient)—against a sleek dark or clean light theme suitable for enterprise dashboards.
|
||||||
|
- **Typography**: Use modern, clean sans-serif fonts (e.g., `Inter` or `Montserrat`) to reflect a professional tech identity.
|
||||||
|
- **Naming Conventions**: Replace generic labels (like "Netify Dashboard") with "BackOne Dashboard" or "BackOne".
|
||||||
|
|
||||||
|
# Architecture Rules: Multi-User & RBAC
|
||||||
|
- **Multi-Tenancy**: All backend queries (Flows, Threats, Devices) MUST eventually be scoped by enant_id or site_uuid to ensure data isolation between clients.
|
||||||
|
- **RBAC (Role-Based Access Control)**:
|
||||||
|
- Users must have defined roles (e.g., SUPER_ADMIN, TENANT_ADMIN, SOC_ANALYST, ENGINEER).
|
||||||
|
- Frontend components and Backend API routes must check user roles before rendering sensitive actions (e.g., hiding "Delete Agent" for Analysts).
|
||||||
|
- **Authentication**: Use a robust session management system (e.g., NextAuth.js or JWT-based auth via backend) before exposing the dashboard.
|
||||||
+334
-187
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,64 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const { getUserByUsername } = require('../database');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const JWT_SECRET = process.env.JWT_SECRET || 'super-secret-backone-key';
|
||||||
|
|
||||||
|
router.post('/login', (req, res) => {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.status(400).json({ error: 'Username and password are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = getUserByUsername(username);
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({ error: 'Invalid credentials' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = bcrypt.compareSync(password, user.password_hash);
|
||||||
|
if (!isValid) {
|
||||||
|
return res.status(401).json({ error: 'Invalid credentials' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = jwt.sign(
|
||||||
|
{ id: user.id, username: user.username, role: user.role, site_uuid: user.site_uuid },
|
||||||
|
JWT_SECRET,
|
||||||
|
{ expiresIn: '1d' }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set HttpOnly cookie
|
||||||
|
res.cookie('token', token, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'strict',
|
||||||
|
maxAge: 24 * 60 * 60 * 1000 // 1 day
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: 'Login successful',
|
||||||
|
user: { id: user.id, username: user.username, role: user.role, site_uuid: user.site_uuid }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/me', (req, res) => {
|
||||||
|
const token = req.cookies?.token;
|
||||||
|
if (!token) {
|
||||||
|
return res.status(401).json({ error: 'Not authenticated' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, JWT_SECRET);
|
||||||
|
res.json({ user: decoded });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(401).json({ error: 'Invalid token' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/logout', (req, res) => {
|
||||||
|
res.clearCookie('token');
|
||||||
|
res.json({ message: 'Logged out successfully' });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
+45
-45
@@ -6,8 +6,8 @@ const { runPoll } = require('../scheduler');
|
|||||||
|
|
||||||
// GET /api/dashboard/summary — kartu ringkasan (top of page)
|
// GET /api/dashboard/summary — kartu ringkasan (top of page)
|
||||||
router.get('/summary', (req, res) => {
|
router.get('/summary', (req, res) => {
|
||||||
const stats = db.getStats();
|
const stats = db.getStats(req.user?.site_uuid);
|
||||||
const timeline = db.getBandwidthTimeline(1);
|
const timeline = db.getBandwidthTimeline(1, req.user?.site_uuid);
|
||||||
const latest = timeline[0] ?? {};
|
const latest = timeline[0] ?? {};
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -27,54 +27,54 @@ router.get('/summary', (req, res) => {
|
|||||||
// GET /api/dashboard/apps — top aplikasi
|
// GET /api/dashboard/apps — top aplikasi
|
||||||
router.get('/apps', (req, res) => {
|
router.get('/apps', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 10);
|
const limit = parseInt(req.query.limit ?? 10);
|
||||||
const data = db.getLatestBandwidthApps(limit);
|
const data = db.getLatestBandwidthApps(limit, req.user?.site_uuid);
|
||||||
res.json({ ok: true, data });
|
res.json({ ok: true, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/dashboard/devices — daftar device
|
// GET /api/dashboard/devices — daftar device
|
||||||
router.get('/devices', (req, res) => {
|
router.get('/devices', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
const data = db.getLatestDevices(limit);
|
const data = db.getLatestDevices(limit, req.user?.site_uuid);
|
||||||
res.json({ ok: true, data });
|
res.json({ ok: true, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/dashboard/flows — flow aktif
|
// GET /api/dashboard/flows — flow aktif
|
||||||
router.get('/flows', (req, res) => {
|
router.get('/flows', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
const data = db.getLatestFlows(limit);
|
const data = db.getLatestFlows(limit, req.user?.site_uuid);
|
||||||
res.json({ ok: true, data });
|
res.json({ ok: true, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/dashboard/threats — ancaman keamanan
|
// GET /api/dashboard/threats — ancaman keamanan
|
||||||
router.get('/threats', (req, res) => {
|
router.get('/threats', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 20);
|
const limit = parseInt(req.query.limit ?? 20);
|
||||||
const data = db.getLatestThreats(limit);
|
const data = db.getLatestThreats(limit, req.user?.site_uuid);
|
||||||
res.json({ ok: true, data });
|
res.json({ ok: true, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/dashboard/protocols — top protokol
|
// GET /api/dashboard/protocols — top protokol
|
||||||
router.get('/protocols', (req, res) => {
|
router.get('/protocols', (req, res) => {
|
||||||
const data = db.getLatestProtocols();
|
const data = db.getLatestProtocols(20, req.user?.site_uuid);
|
||||||
res.json({ ok: true, data });
|
res.json({ ok: true, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/dashboard/countries — top negara
|
// GET /api/dashboard/countries — top negara
|
||||||
router.get('/countries', (req, res) => {
|
router.get('/countries', (req, res) => {
|
||||||
const data = db.getLatestCountries();
|
const data = db.getLatestCountries(15, req.user?.site_uuid);
|
||||||
res.json({ ok: true, data });
|
res.json({ ok: true, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/dashboard/dns — top DNS queries
|
// GET /api/dashboard/dns — top DNS queries
|
||||||
router.get('/dns', (req, res) => {
|
router.get('/dns', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 20);
|
const limit = parseInt(req.query.limit ?? 20);
|
||||||
const data = db.getLatestDNS(limit);
|
const data = db.getLatestDNS(limit, req.user?.site_uuid);
|
||||||
res.json({ ok: true, data });
|
res.json({ ok: true, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/dashboard/events — events terbaru
|
// GET /api/dashboard/events — events terbaru
|
||||||
router.get('/events', (req, res) => {
|
router.get('/events', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 20);
|
const limit = parseInt(req.query.limit ?? 20);
|
||||||
const rawData = db.getLatestEvents(limit);
|
const rawData = db.getLatestEvents(limit, req.user?.site_uuid);
|
||||||
const mappedData = rawData.map(r => ({
|
const mappedData = rawData.map(r => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
event_id: r.event_id,
|
event_id: r.event_id,
|
||||||
@@ -90,7 +90,7 @@ router.get('/events', (req, res) => {
|
|||||||
// GET /api/dashboard/timeline — bandwidth timeline (grafik)
|
// GET /api/dashboard/timeline — bandwidth timeline (grafik)
|
||||||
router.get('/timeline', (req, res) => {
|
router.get('/timeline', (req, res) => {
|
||||||
const points = parseInt(req.query.points ?? 60);
|
const points = parseInt(req.query.points ?? 60);
|
||||||
const data = db.getBandwidthTimeline(points);
|
const data = db.getBandwidthTimeline(points, req.user?.site_uuid);
|
||||||
res.json({ ok: true, data });
|
res.json({ ok: true, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,67 +102,67 @@ router.post('/refresh', async (req, res) => {
|
|||||||
|
|
||||||
// ─── ROUTES FITUR BARU 1-11 ───────────────────────────────────────────────────
|
// ─── ROUTES FITUR BARU 1-11 ───────────────────────────────────────────────────
|
||||||
router.get('/app-categories', (req, res) => {
|
router.get('/app-categories', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('app_categories', 'download', 15) });
|
res.json({ ok: true, data: db.getLatest('app_categories', 'download', 15, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/continents', (req, res) => {
|
router.get('/continents', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('continents', 'download', 10) });
|
res.json({ ok: true, data: db.getLatest('continents', 'download', 10, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/regions', (req, res) => {
|
router.get('/regions', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('regions', 'download', 20) });
|
res.json({ ok: true, data: db.getLatest('regions', 'download', 20, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/cities', (req, res) => {
|
router.get('/cities', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('cities', 'download', 20) });
|
res.json({ ok: true, data: db.getLatest('cities', 'download', 20, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/vlans', (req, res) => {
|
router.get('/vlans', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('vlans', 'download', 20) });
|
res.json({ ok: true, data: db.getLatest('vlans', 'download', 20, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/interfaces', (req, res) => {
|
router.get('/interfaces', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('interfaces', 'download', 20) });
|
res.json({ ok: true, data: db.getLatest('interfaces', 'download', 20, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/flow-types', (req, res) => {
|
router.get('/flow-types', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('flow_types', 'download', 10) });
|
res.json({ ok: true, data: db.getLatest('flow_types', 'download', 10, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/flow-origins', (req, res) => {
|
router.get('/flow-origins', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('flow_origins', 'download', 10) });
|
res.json({ ok: true, data: db.getLatest('flow_origins', 'download', 10, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/ip-versions', (req, res) => {
|
router.get('/ip-versions', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('ip_versions', 'download', 5) });
|
res.json({ ok: true, data: db.getLatest('ip_versions', 'download', 5, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/remote-ips', (req, res) => {
|
router.get('/remote-ips', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 20);
|
const limit = parseInt(req.query.limit ?? 20);
|
||||||
res.json({ ok: true, data: db.getLatest('remote_ips', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('remote_ips', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
router.get('/mac-bandwidth', (req, res) => {
|
router.get('/mac-bandwidth', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
res.json({ ok: true, data: db.getLatest('mac_bandwidth', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('mac_bandwidth', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── FIX: ROUTES YANG SEBELUMNYA HILANG ──────────────────────────────────────
|
// ─── FIX: ROUTES YANG SEBELUMNYA HILANG ──────────────────────────────────────
|
||||||
|
|
||||||
// TLS Versions
|
// TLS Versions
|
||||||
router.get('/tls-versions', (req, res) => {
|
router.get('/tls-versions', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('tls_versions', 'download', 10) });
|
res.json({ ok: true, data: db.getLatest('tls_versions', 'download', 10, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// TLS Ciphers
|
// TLS Ciphers
|
||||||
router.get('/tls-ciphers', (req, res) => {
|
router.get('/tls-ciphers', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('tls_ciphers', 'download', 15) });
|
res.json({ ok: true, data: db.getLatest('tls_ciphers', 'download', 15, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// TLS Security Level
|
// TLS Security Level
|
||||||
router.get('/tls-security', (req, res) => {
|
router.get('/tls-security', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('tls_security', 'download', 10) });
|
res.json({ ok: true, data: db.getLatest('tls_security', 'download', 10, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// NetBIOS Hostnames (Windows devices)
|
// NetBIOS Hostnames (Windows devices)
|
||||||
router.get('/netbios', (req, res) => {
|
router.get('/netbios', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 30);
|
const limit = parseInt(req.query.limit ?? 30);
|
||||||
res.json({ ok: true, data: db.getLatest('netbios_hostnames', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('netbios_hostnames', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Discovery OS (sistem operasi yang terdeteksi)
|
// Discovery OS (sistem operasi yang terdeteksi)
|
||||||
router.get('/discovery-os', (req, res) => {
|
router.get('/discovery-os', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getLatest('discovery_os', 'download', 20) });
|
res.json({ ok: true, data: db.getLatest('discovery_os', 'download', 20, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── ROUTES DPI 12-21 ─────────────────────────────────────────────────────────
|
// ─── ROUTES DPI 12-21 ─────────────────────────────────────────────────────────
|
||||||
@@ -170,110 +170,110 @@ router.get('/discovery-os', (req, res) => {
|
|||||||
// 12. DHCP Class Fingerprint
|
// 12. DHCP Class Fingerprint
|
||||||
router.get('/dhcp-fingerprints', (req, res) => {
|
router.get('/dhcp-fingerprints', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 30);
|
const limit = parseInt(req.query.limit ?? 30);
|
||||||
res.json({ ok: true, data: db.getLatest('dhcp_fingerprints', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('dhcp_fingerprints', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 13. HTTP User-Agent
|
// 13. HTTP User-Agent
|
||||||
router.get('/http-user-agents', (req, res) => {
|
router.get('/http-user-agents', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 30);
|
const limit = parseInt(req.query.limit ?? 30);
|
||||||
res.json({ ok: true, data: db.getLatest('http_user_agents', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('http_user_agents', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 14. HTTPS SNI Hostname
|
// 14. HTTPS SNI Hostname
|
||||||
router.get('/sni-hostnames', (req, res) => {
|
router.get('/sni-hostnames', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 30);
|
const limit = parseInt(req.query.limit ?? 30);
|
||||||
res.json({ ok: true, data: db.getLatest('sni_hostnames', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('sni_hostnames', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 15. SSL Server Common Name
|
// 15. SSL Server Common Name
|
||||||
router.get('/ssl-server-cn', (req, res) => {
|
router.get('/ssl-server-cn', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 30);
|
const limit = parseInt(req.query.limit ?? 30);
|
||||||
res.json({ ok: true, data: db.getLatest('ssl_server_cn', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('ssl_server_cn', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 17. QUIC Hostname
|
// 17. QUIC Hostname
|
||||||
router.get('/quic-hostnames', (req, res) => {
|
router.get('/quic-hostnames', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 30);
|
const limit = parseInt(req.query.limit ?? 30);
|
||||||
res.json({ ok: true, data: db.getLatest('quic_hostnames', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('quic_hostnames', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 18. BitTorrent Info Hash
|
// 18. BitTorrent Info Hash
|
||||||
router.get('/bittorrent-hashes', (req, res) => {
|
router.get('/bittorrent-hashes', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 30);
|
const limit = parseInt(req.query.limit ?? 30);
|
||||||
res.json({ ok: true, data: db.getLatest('bittorrent_hashes', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('bittorrent_hashes', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 19. SSH Version
|
// 19. SSH Version
|
||||||
router.get('/ssh-versions', (req, res) => {
|
router.get('/ssh-versions', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 20);
|
const limit = parseInt(req.query.limit ?? 20);
|
||||||
res.json({ ok: true, data: db.getLatest('ssh_versions', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('ssh_versions', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 21. mDNS Hostname (Chromecast, Apple TV, etc.)
|
// 21. mDNS Hostname (Chromecast, Apple TV, etc.)
|
||||||
router.get('/mdns-hostnames', (req, res) => {
|
router.get('/mdns-hostnames', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 30);
|
const limit = parseInt(req.query.limit ?? 30);
|
||||||
res.json({ ok: true, data: db.getLatest('mdns_hostnames', 'download', limit) });
|
res.json({ ok: true, data: db.getLatest('mdns_hostnames', 'download', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── INTELLIGENCE ROUTES 22-30 ────────────────────────────────────────────────
|
// ─── INTELLIGENCE ROUTES 22-30 ────────────────────────────────────────────────
|
||||||
|
|
||||||
// Stats ringkasan semua intelligence (untuk badge count di tab)
|
// Stats ringkasan semua intelligence (untuk badge count di tab)
|
||||||
router.get('/intelligence/stats', (req, res) => {
|
router.get('/intelligence/stats', (req, res) => {
|
||||||
res.json({ ok: true, data: db.getIntelStats() });
|
res.json({ ok: true, data: db.getIntelStats(req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 22. Cryptocurrency Mining
|
// 22. Cryptocurrency Mining
|
||||||
router.get('/intelligence/crypto-mining', (req, res) => {
|
router.get('/intelligence/crypto-mining', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_crypto_mining', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_crypto_mining', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 23. Device Discovery
|
// 23. Device Discovery
|
||||||
router.get('/intelligence/device-discovery', (req, res) => {
|
router.get('/intelligence/device-discovery', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 100);
|
const limit = parseInt(req.query.limit ?? 100);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_device_discovery', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_device_discovery', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 24. Encryption Audit
|
// 24. Encryption Audit
|
||||||
router.get('/intelligence/encryption-audit', (req, res) => {
|
router.get('/intelligence/encryption-audit', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_encryption_audit', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_encryption_audit', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 25. Insecure Protocols
|
// 25. Insecure Protocols
|
||||||
router.get('/intelligence/insecure-protocols', (req, res) => {
|
router.get('/intelligence/insecure-protocols', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_insecure_protocols', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_insecure_protocols', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 26. IP Reputation
|
// 26. IP Reputation
|
||||||
router.get('/intelligence/ip-reputation', (req, res) => {
|
router.get('/intelligence/ip-reputation', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_ip_reputation', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_ip_reputation', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 27. Server Discovery
|
// 27. Server Discovery
|
||||||
router.get('/intelligence/server-discovery', (req, res) => {
|
router.get('/intelligence/server-discovery', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 100);
|
const limit = parseInt(req.query.limit ?? 100);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_server_discovery', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_server_discovery', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 28. Tor Detection
|
// 28. Tor Detection
|
||||||
router.get('/intelligence/tor', (req, res) => {
|
router.get('/intelligence/tor', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_tor_detection', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_tor_detection', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 29. Unencrypted Passwords
|
// 29. Unencrypted Passwords
|
||||||
router.get('/intelligence/unencrypted-passwords', (req, res) => {
|
router.get('/intelligence/unencrypted-passwords', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_unencrypted_passwords', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_unencrypted_passwords', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 30. VPN Detection
|
// 30. VPN Detection
|
||||||
router.get('/intelligence/vpn', (req, res) => {
|
router.get('/intelligence/vpn', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit ?? 50);
|
const limit = parseInt(req.query.limit ?? 50);
|
||||||
res.json({ ok: true, data: db.getIntelData('intel_vpn_detection', limit) });
|
res.json({ ok: true, data: db.getIntelData('intel_vpn_detection', limit, req.user?.site_uuid) });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── LOOKUP ROUTES ────────────────────────────────────────────────────────────
|
// ─── LOOKUP ROUTES ────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+44
-43
@@ -2,6 +2,7 @@
|
|||||||
const cron = require('node-cron');
|
const cron = require('node-cron');
|
||||||
const netify = require('./netify');
|
const netify = require('./netify');
|
||||||
const db = require('./database');
|
const db = require('./database');
|
||||||
|
const SITE_UUID = process.env.NETIFY_SITE_UUID || 'dummy_site_uuid';
|
||||||
|
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ async function runPoll() {
|
|||||||
// 1. Top Aplikasi
|
// 1. Top Aplikasi
|
||||||
const apps = await netify.fetchTopApps(1440, 20);
|
const apps = await netify.fetchTopApps(1440, 20);
|
||||||
if (apps && Array.isArray(apps)) {
|
if (apps && Array.isArray(apps)) {
|
||||||
db.insertBandwidthApps(apps, fetchedAt);
|
db.insertBandwidthApps(apps, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK Apps : ${apps.length} baris`);
|
console.log(`[Scheduler] OK Apps : ${apps.length} baris`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Scheduler] -- Apps : tidak ada data`);
|
console.log(`[Scheduler] -- Apps : tidak ada data`);
|
||||||
@@ -27,7 +28,7 @@ async function runPoll() {
|
|||||||
// 2. Top Devices — pakai fetchDiscoveredDevices yg sudah dinormalisasi
|
// 2. Top Devices — pakai fetchDiscoveredDevices yg sudah dinormalisasi
|
||||||
const devices = await netify.fetchDiscoveredDevices(1440, 200);
|
const devices = await netify.fetchDiscoveredDevices(1440, 200);
|
||||||
if (devices && Array.isArray(devices)) {
|
if (devices && Array.isArray(devices)) {
|
||||||
db.insertDevices(devices, fetchedAt);
|
db.insertDevices(devices, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK Devices : ${devices.length} baris`);
|
console.log(`[Scheduler] OK Devices : ${devices.length} baris`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Scheduler] -- Devices : tidak ada data`);
|
console.log(`[Scheduler] -- Devices : tidak ada data`);
|
||||||
@@ -36,7 +37,7 @@ async function runPoll() {
|
|||||||
// 3. Top Protokol
|
// 3. Top Protokol
|
||||||
const protocols = await netify.fetchTopProtocols(1440, 20);
|
const protocols = await netify.fetchTopProtocols(1440, 20);
|
||||||
if (protocols && Array.isArray(protocols)) {
|
if (protocols && Array.isArray(protocols)) {
|
||||||
db.insertProtocols(protocols, fetchedAt);
|
db.insertProtocols(protocols, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK Protocols : ${protocols.length} baris`);
|
console.log(`[Scheduler] OK Protocols : ${protocols.length} baris`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Scheduler] -- Protocols : tidak ada data`);
|
console.log(`[Scheduler] -- Protocols : tidak ada data`);
|
||||||
@@ -45,7 +46,7 @@ async function runPoll() {
|
|||||||
// 4. Top Negara
|
// 4. Top Negara
|
||||||
const countries = await netify.fetchTopCountries(1440, 15);
|
const countries = await netify.fetchTopCountries(1440, 15);
|
||||||
if (countries && Array.isArray(countries)) {
|
if (countries && Array.isArray(countries)) {
|
||||||
db.insertCountries(countries, fetchedAt);
|
db.insertCountries(countries, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK Countries : ${countries.length} baris`);
|
console.log(`[Scheduler] OK Countries : ${countries.length} baris`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Scheduler] -- Countries : tidak ada data`);
|
console.log(`[Scheduler] -- Countries : tidak ada data`);
|
||||||
@@ -54,7 +55,7 @@ async function runPoll() {
|
|||||||
// 5. Top Domain/DNS
|
// 5. Top Domain/DNS
|
||||||
const domains = await netify.fetchTopDomains(1440, 20);
|
const domains = await netify.fetchTopDomains(1440, 20);
|
||||||
if (domains && Array.isArray(domains)) {
|
if (domains && Array.isArray(domains)) {
|
||||||
db.insertDNS(domains, fetchedAt);
|
db.insertDNS(domains, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK DNS : ${domains.length} baris`);
|
console.log(`[Scheduler] OK DNS : ${domains.length} baris`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Scheduler] -- DNS : tidak ada data`);
|
console.log(`[Scheduler] -- DNS : tidak ada data`);
|
||||||
@@ -63,7 +64,7 @@ async function runPoll() {
|
|||||||
// 6. Flows — pakai local_ip sebagai proxy
|
// 6. Flows — pakai local_ip sebagai proxy
|
||||||
const flows = await netify.fetchFlows(200);
|
const flows = await netify.fetchFlows(200);
|
||||||
if (flows && Array.isArray(flows)) {
|
if (flows && Array.isArray(flows)) {
|
||||||
db.insertFlows(flows, fetchedAt);
|
db.insertFlows(flows, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK Flows : ${flows.length} baris`);
|
console.log(`[Scheduler] OK Flows : ${flows.length} baris`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Scheduler] -- Flows : tidak ada data`);
|
console.log(`[Scheduler] -- Flows : tidak ada data`);
|
||||||
@@ -72,7 +73,7 @@ async function runPoll() {
|
|||||||
// 7. Threats — dari Events Status
|
// 7. Threats — dari Events Status
|
||||||
const threats = await netify.fetchCyberThreats(1440, 50);
|
const threats = await netify.fetchCyberThreats(1440, 50);
|
||||||
if (threats && Array.isArray(threats)) {
|
if (threats && Array.isArray(threats)) {
|
||||||
db.insertThreats(threats, fetchedAt);
|
db.insertThreats(threats, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK Threats : ${threats.length} baris`);
|
console.log(`[Scheduler] OK Threats : ${threats.length} baris`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Scheduler] -- Threats : tidak ada data`);
|
console.log(`[Scheduler] -- Threats : tidak ada data`);
|
||||||
@@ -81,7 +82,7 @@ async function runPoll() {
|
|||||||
// 8. Events Log
|
// 8. Events Log
|
||||||
const events = await netify.fetchEvents(50);
|
const events = await netify.fetchEvents(50);
|
||||||
if (events && Array.isArray(events)) {
|
if (events && Array.isArray(events)) {
|
||||||
db.insertEvents(events, fetchedAt);
|
db.insertEvents(events, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK Events : ${events.length} baris`);
|
console.log(`[Scheduler] OK Events : ${events.length} baris`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Scheduler] -- Events : tidak ada data`);
|
console.log(`[Scheduler] -- Events : tidak ada data`);
|
||||||
@@ -89,180 +90,180 @@ async function runPoll() {
|
|||||||
|
|
||||||
// 10. App Categories
|
// 10. App Categories
|
||||||
const appCats = await netify.fetchTopAppCategories(1440, 15);
|
const appCats = await netify.fetchTopAppCategories(1440, 15);
|
||||||
if (appCats?.length) { db.insertAppCategories(appCats, fetchedAt); console.log(`[Scheduler] OK AppCats : ${appCats.length} baris`); }
|
if (appCats?.length) { db.insertAppCategories(appCats, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK AppCats : ${appCats.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- AppCats : tidak ada data`);
|
else console.log(`[Scheduler] -- AppCats : tidak ada data`);
|
||||||
|
|
||||||
// 11. Continents
|
// 11. Continents
|
||||||
const continents = await netify.fetchTopContinents(1440, 10);
|
const continents = await netify.fetchTopContinents(1440, 10);
|
||||||
if (continents?.length) { db.insertContinents(continents, fetchedAt); console.log(`[Scheduler] OK Continents : ${continents.length} baris`); }
|
if (continents?.length) { db.insertContinents(continents, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK Continents : ${continents.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- Continents : tidak ada data`);
|
else console.log(`[Scheduler] -- Continents : tidak ada data`);
|
||||||
|
|
||||||
// 12. Regions
|
// 12. Regions
|
||||||
const regions = await netify.fetchTopRegions(1440, 20);
|
const regions = await netify.fetchTopRegions(1440, 20);
|
||||||
if (regions?.length) { db.insertRegions(regions, fetchedAt); console.log(`[Scheduler] OK Regions : ${regions.length} baris`); }
|
if (regions?.length) { db.insertRegions(regions, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK Regions : ${regions.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- Regions : tidak ada data`);
|
else console.log(`[Scheduler] -- Regions : tidak ada data`);
|
||||||
|
|
||||||
// 13. Cities
|
// 13. Cities
|
||||||
const cities = await netify.fetchTopCities(1440, 20);
|
const cities = await netify.fetchTopCities(1440, 20);
|
||||||
if (cities?.length) { db.insertCities(cities, fetchedAt); console.log(`[Scheduler] OK Cities : ${cities.length} baris`); }
|
if (cities?.length) { db.insertCities(cities, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK Cities : ${cities.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- Cities : tidak ada data`);
|
else console.log(`[Scheduler] -- Cities : tidak ada data`);
|
||||||
|
|
||||||
// 14. VLANs
|
// 14. VLANs
|
||||||
const vlans = await netify.fetchTopVLANs(1440, 20);
|
const vlans = await netify.fetchTopVLANs(1440, 20);
|
||||||
if (vlans?.length) { db.insertVLANs(vlans, fetchedAt); console.log(`[Scheduler] OK VLANs : ${vlans.length} baris`); }
|
if (vlans?.length) { db.insertVLANs(vlans, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK VLANs : ${vlans.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- VLANs : tidak ada data`);
|
else console.log(`[Scheduler] -- VLANs : tidak ada data`);
|
||||||
|
|
||||||
// 15. Interfaces
|
// 15. Interfaces
|
||||||
const ifaces = await netify.fetchTopInterfaces(1440, 20);
|
const ifaces = await netify.fetchTopInterfaces(1440, 20);
|
||||||
if (ifaces?.length) { db.insertInterfaces(ifaces, fetchedAt); console.log(`[Scheduler] OK Interfaces : ${ifaces.length} baris`); }
|
if (ifaces?.length) { db.insertInterfaces(ifaces, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK Interfaces : ${ifaces.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- Interfaces : tidak ada data`);
|
else console.log(`[Scheduler] -- Interfaces : tidak ada data`);
|
||||||
|
|
||||||
// 16. Flow Types
|
// 16. Flow Types
|
||||||
const flowTypes = await netify.fetchTopFlowTypes(1440, 10);
|
const flowTypes = await netify.fetchTopFlowTypes(1440, 10);
|
||||||
if (flowTypes?.length) { db.insertFlowTypes(flowTypes, fetchedAt); console.log(`[Scheduler] OK FlowTypes : ${flowTypes.length} baris`); }
|
if (flowTypes?.length) { db.insertFlowTypes(flowTypes, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK FlowTypes : ${flowTypes.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- FlowTypes : tidak ada data`);
|
else console.log(`[Scheduler] -- FlowTypes : tidak ada data`);
|
||||||
|
|
||||||
// 17. Flow Origins
|
// 17. Flow Origins
|
||||||
const flowOrigins = await netify.fetchTopFlowOrigins(1440, 10);
|
const flowOrigins = await netify.fetchTopFlowOrigins(1440, 10);
|
||||||
if (flowOrigins?.length) { db.insertFlowOrigins(flowOrigins, fetchedAt); console.log(`[Scheduler] OK FlowOrigin : ${flowOrigins.length} baris`); }
|
if (flowOrigins?.length) { db.insertFlowOrigins(flowOrigins, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK FlowOrigin : ${flowOrigins.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- FlowOrigin : tidak ada data`);
|
else console.log(`[Scheduler] -- FlowOrigin : tidak ada data`);
|
||||||
|
|
||||||
// 18. IP Versions
|
// 18. IP Versions
|
||||||
const ipVersions = await netify.fetchTopIPVersions(1440, 5);
|
const ipVersions = await netify.fetchTopIPVersions(1440, 5);
|
||||||
if (ipVersions?.length) { db.insertIPVersions(ipVersions, fetchedAt); console.log(`[Scheduler] OK IPVersions : ${ipVersions.length} baris`); }
|
if (ipVersions?.length) { db.insertIPVersions(ipVersions, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK IPVersions : ${ipVersions.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- IPVersions : tidak ada data`);
|
else console.log(`[Scheduler] -- IPVersions : tidak ada data`);
|
||||||
|
|
||||||
// 19. Remote IPs
|
// 19. Remote IPs
|
||||||
const remoteIPs = await netify.fetchTopRemoteIPs(1440, 20);
|
const remoteIPs = await netify.fetchTopRemoteIPs(1440, 20);
|
||||||
if (remoteIPs?.length) { db.insertRemoteIPs(remoteIPs, fetchedAt); console.log(`[Scheduler] OK RemoteIPs : ${remoteIPs.length} baris`); }
|
if (remoteIPs?.length) { db.insertRemoteIPs(remoteIPs, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK RemoteIPs : ${remoteIPs.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- RemoteIPs : tidak ada data`);
|
else console.log(`[Scheduler] -- RemoteIPs : tidak ada data`);
|
||||||
|
|
||||||
// 20. MAC Bandwidth
|
// 20. MAC Bandwidth
|
||||||
const macBW = await netify.fetchTopLocalMACs(1440, 50);
|
const macBW = await netify.fetchTopLocalMACs(1440, 50);
|
||||||
if (macBW?.length) { db.insertMACBandwidth(macBW, fetchedAt); console.log(`[Scheduler] OK MACBandwdh : ${macBW.length} baris`); }
|
if (macBW?.length) { db.insertMACBandwidth(macBW, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK MACBandwdh : ${macBW.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- MACBandwdh : tidak ada data`);
|
else console.log(`[Scheduler] -- MACBandwdh : tidak ada data`);
|
||||||
|
|
||||||
// 9. Bandwidth Timeline
|
// 9. Bandwidth Timeline
|
||||||
const summary = await netify.fetchBandwidthSummary(1440);
|
const summary = await netify.fetchBandwidthSummary(1440);
|
||||||
const devCount = devices?.length ?? 0;
|
const devCount = devices?.length ?? 0;
|
||||||
db.insertBandwidthTimeline({ ...summary, devices: devCount }, fetchedAt);
|
db.insertBandwidthTimeline({ ...summary, devices: devCount }, fetchedAt, SITE_UUID);
|
||||||
console.log(`[Scheduler] OK Timeline : saved`);
|
console.log(`[Scheduler] OK Timeline : saved`);
|
||||||
|
|
||||||
// 21. TLS Versions
|
// 21. TLS Versions
|
||||||
const tlsVer = await netify.fetchTLSVersions(1440, 10);
|
const tlsVer = await netify.fetchTLSVersions(1440, 10);
|
||||||
if (tlsVer?.length) { db.insertTLSVersions(tlsVer, fetchedAt); console.log(`[Scheduler] OK TLS Ver : ${tlsVer.length} baris`); }
|
if (tlsVer?.length) { db.insertTLSVersions(tlsVer, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK TLS Ver : ${tlsVer.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- TLS Ver : tidak ada data`);
|
else console.log(`[Scheduler] -- TLS Ver : tidak ada data`);
|
||||||
|
|
||||||
// 22. TLS Ciphers
|
// 22. TLS Ciphers
|
||||||
const tlsCipher = await netify.fetchTLSCiphers(1440, 15);
|
const tlsCipher = await netify.fetchTLSCiphers(1440, 15);
|
||||||
if (tlsCipher?.length) { db.insertTLSCiphers(tlsCipher, fetchedAt); console.log(`[Scheduler] OK TLS Cipher : ${tlsCipher.length} baris`); }
|
if (tlsCipher?.length) { db.insertTLSCiphers(tlsCipher, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK TLS Cipher : ${tlsCipher.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- TLS Cipher : tidak ada data`);
|
else console.log(`[Scheduler] -- TLS Cipher : tidak ada data`);
|
||||||
|
|
||||||
// 23. TLS Security
|
// 23. TLS Security
|
||||||
const tlsSec = await netify.fetchTLSSecurity(1440, 10);
|
const tlsSec = await netify.fetchTLSSecurity(1440, 10);
|
||||||
if (tlsSec?.length) { db.insertTLSSecurity(tlsSec, fetchedAt); console.log(`[Scheduler] OK TLS Sec : ${tlsSec.length} baris`); }
|
if (tlsSec?.length) { db.insertTLSSecurity(tlsSec, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK TLS Sec : ${tlsSec.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- TLS Sec : tidak ada data`);
|
else console.log(`[Scheduler] -- TLS Sec : tidak ada data`);
|
||||||
|
|
||||||
// 24. NetBIOS Hostnames
|
// 24. NetBIOS Hostnames
|
||||||
const netbios = await netify.fetchNetBIOSHostnames(1440, 30);
|
const netbios = await netify.fetchNetBIOSHostnames(1440, 30);
|
||||||
if (netbios?.length) { db.insertNetBIOSHostnames(netbios, fetchedAt); console.log(`[Scheduler] OK NetBIOS : ${netbios.length} baris`); }
|
if (netbios?.length) { db.insertNetBIOSHostnames(netbios, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK NetBIOS : ${netbios.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- NetBIOS : tidak ada data`);
|
else console.log(`[Scheduler] -- NetBIOS : tidak ada data`);
|
||||||
|
|
||||||
// 25. Discovery OS (standalone — OS yang terdeteksi di jaringan)
|
// 25. Discovery OS (standalone — OS yang terdeteksi di jaringan)
|
||||||
const discOs = await netify.fetchTopDiscoveryOS(1440, 20);
|
const discOs = await netify.fetchTopDiscoveryOS(1440, 20);
|
||||||
if (discOs?.length) { db.insertDiscoveryOS(discOs, fetchedAt); console.log(`[Scheduler] OK DiscOS : ${discOs.length} baris`); }
|
if (discOs?.length) { db.insertDiscoveryOS(discOs, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK DiscOS : ${discOs.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- DiscOS : tidak ada data`);
|
else console.log(`[Scheduler] -- DiscOS : tidak ada data`);
|
||||||
|
|
||||||
// 26. DHCP Class Fingerprint
|
// 26. DHCP Class Fingerprint
|
||||||
const dhcpFp = await netify.fetchDHCPClassFingerprints(1440, 30);
|
const dhcpFp = await netify.fetchDHCPClassFingerprints(1440, 30);
|
||||||
if (dhcpFp?.length) { db.insertDHCPFingerprints(dhcpFp, fetchedAt); console.log(`[Scheduler] OK DHCP FP : ${dhcpFp.length} baris`); }
|
if (dhcpFp?.length) { db.insertDHCPFingerprints(dhcpFp, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK DHCP FP : ${dhcpFp.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- DHCP FP : tidak ada data`);
|
else console.log(`[Scheduler] -- DHCP FP : tidak ada data`);
|
||||||
|
|
||||||
// 27. HTTP User-Agent
|
// 27. HTTP User-Agent
|
||||||
const userAgents = await netify.fetchHTTPUserAgents(1440, 30);
|
const userAgents = await netify.fetchHTTPUserAgents(1440, 30);
|
||||||
if (userAgents?.length) { db.insertHTTPUserAgents(userAgents, fetchedAt); console.log(`[Scheduler] OK UserAgent : ${userAgents.length} baris`); }
|
if (userAgents?.length) { db.insertHTTPUserAgents(userAgents, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK UserAgent : ${userAgents.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- UserAgent : tidak ada data`);
|
else console.log(`[Scheduler] -- UserAgent : tidak ada data`);
|
||||||
|
|
||||||
// 28. HTTPS SNI Hostname
|
// 28. HTTPS SNI Hostname
|
||||||
const sniHosts = await netify.fetchSNIHostnames(1440, 30);
|
const sniHosts = await netify.fetchSNIHostnames(1440, 30);
|
||||||
if (sniHosts?.length) { db.insertSNIHostnames(sniHosts, fetchedAt); console.log(`[Scheduler] OK SNI Host : ${sniHosts.length} baris`); }
|
if (sniHosts?.length) { db.insertSNIHostnames(sniHosts, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK SNI Host : ${sniHosts.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- SNI Host : tidak ada data`);
|
else console.log(`[Scheduler] -- SNI Host : tidak ada data`);
|
||||||
|
|
||||||
// 29. SSL Server Common Name
|
// 29. SSL Server Common Name
|
||||||
const sslCN = await netify.fetchSSLServerCN(1440, 30);
|
const sslCN = await netify.fetchSSLServerCN(1440, 30);
|
||||||
if (sslCN?.length) { db.insertSSLServerCN(sslCN, fetchedAt); console.log(`[Scheduler] OK SSL CN : ${sslCN.length} baris`); }
|
if (sslCN?.length) { db.insertSSLServerCN(sslCN, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK SSL CN : ${sslCN.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- SSL CN : tidak ada data`);
|
else console.log(`[Scheduler] -- SSL CN : tidak ada data`);
|
||||||
|
|
||||||
// 30. QUIC Hostname
|
// 30. QUIC Hostname
|
||||||
const quicHosts = await netify.fetchQUICHostnames(1440, 30);
|
const quicHosts = await netify.fetchQUICHostnames(1440, 30);
|
||||||
if (quicHosts?.length) { db.insertQUICHostnames(quicHosts, fetchedAt); console.log(`[Scheduler] OK QUIC Host : ${quicHosts.length} baris`); }
|
if (quicHosts?.length) { db.insertQUICHostnames(quicHosts, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK QUIC Host : ${quicHosts.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- QUIC Host : tidak ada data`);
|
else console.log(`[Scheduler] -- QUIC Host : tidak ada data`);
|
||||||
|
|
||||||
// 31. BitTorrent Info Hash
|
// 31. BitTorrent Info Hash
|
||||||
const btHashes = await netify.fetchBitTorrentInfoHashes(1440, 30);
|
const btHashes = await netify.fetchBitTorrentInfoHashes(1440, 30);
|
||||||
if (btHashes?.length) { db.insertBitTorrentHashes(btHashes, fetchedAt); console.log(`[Scheduler] OK BT Hash : ${btHashes.length} baris`); }
|
if (btHashes?.length) { db.insertBitTorrentHashes(btHashes, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK BT Hash : ${btHashes.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- BT Hash : tidak ada data`);
|
else console.log(`[Scheduler] -- BT Hash : tidak ada data`);
|
||||||
|
|
||||||
// 32. SSH Client (field: ssh_client)
|
// 32. SSH Client (field: ssh_client)
|
||||||
const sshClient = await netify.fetchSSHClients(1440, 20);
|
const sshClient = await netify.fetchSSHClients(1440, 20);
|
||||||
if (sshClient?.length) { db.insertSSHVersions(sshClient, fetchedAt); console.log(`[Scheduler] OK SSH Client : ${sshClient.length} baris`); }
|
if (sshClient?.length) { db.insertSSHVersions(sshClient, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK SSH Client : ${sshClient.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- SSH Client : tidak ada data`);
|
else console.log(`[Scheduler] -- SSH Client : tidak ada data`);
|
||||||
|
|
||||||
// 32b. SSH Server (field: ssh_server)
|
// 32b. SSH Server (field: ssh_server)
|
||||||
const sshServer = await netify.fetchSSHServers(1440, 20);
|
const sshServer = await netify.fetchSSHServers(1440, 20);
|
||||||
if (sshServer?.length) { db.insertSSHVersions(sshServer, fetchedAt); console.log(`[Scheduler] OK SSH Server : ${sshServer.length} baris`); }
|
if (sshServer?.length) { db.insertSSHVersions(sshServer, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK SSH Server : ${sshServer.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- SSH Server : tidak ada data`);
|
else console.log(`[Scheduler] -- SSH Server : tidak ada data`);
|
||||||
|
|
||||||
// 33. mDNS Hostname (Chromecast, Apple TV, etc.)
|
// 33. mDNS Hostname (Chromecast, Apple TV, etc.)
|
||||||
const mdnsHosts = await netify.fetchMDNSHostnames(1440, 30);
|
const mdnsHosts = await netify.fetchMDNSHostnames(1440, 30);
|
||||||
if (mdnsHosts?.length) { db.insertMDNSHostnames(mdnsHosts, fetchedAt); console.log(`[Scheduler] OK mDNS Host : ${mdnsHosts.length} baris`); }
|
if (mdnsHosts?.length) { db.insertMDNSHostnames(mdnsHosts, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK mDNS Host : ${mdnsHosts.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- mDNS Host : tidak ada data`);
|
else console.log(`[Scheduler] -- mDNS Host : tidak ada data`);
|
||||||
|
|
||||||
// ─── INTELLIGENCE 22-30 (derive dari data yang tersedia) ─────────────────
|
// ─── INTELLIGENCE 22-30 (derive dari data yang tersedia) ─────────────────
|
||||||
|
|
||||||
// 34. Cryptocurrency Mining (derive dari apps + flows ke port mining)
|
// 34. Cryptocurrency Mining (derive dari apps + flows ke port mining)
|
||||||
const cryptoMining = await netify.fetchCryptoMining(50);
|
const cryptoMining = await netify.fetchCryptoMining(50);
|
||||||
if (cryptoMining?.length) { db.insertCryptoMining(cryptoMining, fetchedAt); console.log(`[Scheduler] OK CryptoMine : ${cryptoMining.length} baris`); }
|
if (cryptoMining?.length) { db.insertCryptoMining(cryptoMining, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK CryptoMine : ${cryptoMining.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- CryptoMine : tidak ada data`);
|
else console.log(`[Scheduler] -- CryptoMine : tidak ada data`);
|
||||||
|
|
||||||
// 35. Device Discovery (derive dari flows + bandwidth per-IP)
|
// 35. Device Discovery (derive dari flows + bandwidth per-IP)
|
||||||
const devDisc = await netify.fetchDeviceDiscovery(100);
|
const devDisc = await netify.fetchDeviceDiscovery(100);
|
||||||
if (devDisc?.length) { db.insertDeviceDiscovery(devDisc, fetchedAt); console.log(`[Scheduler] OK DevDisc : ${devDisc.length} baris`); }
|
if (devDisc?.length) { db.insertDeviceDiscovery(devDisc, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK DevDisc : ${devDisc.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- DevDisc : tidak ada data`);
|
else console.log(`[Scheduler] -- DevDisc : tidak ada data`);
|
||||||
|
|
||||||
// 36. Encryption Audit (derive dari flows per-IP: port encrypted vs plain)
|
// 36. Encryption Audit (derive dari flows per-IP: port encrypted vs plain)
|
||||||
const encAudit = await netify.fetchEncryptionAudit(50);
|
const encAudit = await netify.fetchEncryptionAudit(50);
|
||||||
if (encAudit?.length) { db.insertEncryptionAudit(encAudit, fetchedAt); console.log(`[Scheduler] OK EncAudit : ${encAudit.length} baris`); }
|
if (encAudit?.length) { db.insertEncryptionAudit(encAudit, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK EncAudit : ${encAudit.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- EncAudit : tidak ada data`);
|
else console.log(`[Scheduler] -- EncAudit : tidak ada data`);
|
||||||
|
|
||||||
// 37. Insecure Protocols (derive dari top protocols)
|
// 37. Insecure Protocols (derive dari top protocols)
|
||||||
const insecProto = await netify.fetchInsecureProtocols(1440, 50);
|
const insecProto = await netify.fetchInsecureProtocols(1440, 50);
|
||||||
if (insecProto?.length) { db.insertInsecureProtocols(insecProto, fetchedAt); console.log(`[Scheduler] OK InsecProto : ${insecProto.length} baris`); }
|
if (insecProto?.length) { db.insertInsecureProtocols(insecProto, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK InsecProto : ${insecProto.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- InsecProto : tidak ada data`);
|
else console.log(`[Scheduler] -- InsecProto : tidak ada data`);
|
||||||
|
|
||||||
// 38. IP Reputation (derive dari top remote_ip + high-risk countries)
|
// 38. IP Reputation (derive dari top remote_ip + high-risk countries)
|
||||||
const ipRep = await netify.fetchIPReputation(50);
|
const ipRep = await netify.fetchIPReputation(50);
|
||||||
if (ipRep?.length) { db.insertIPReputation(ipRep, fetchedAt); console.log(`[Scheduler] OK IPRepute : ${ipRep.length} baris`); }
|
if (ipRep?.length) { db.insertIPReputation(ipRep, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK IPRepute : ${ipRep.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- IPRepute : tidak ada data`);
|
else console.log(`[Scheduler] -- IPRepute : tidak ada data`);
|
||||||
|
|
||||||
// 39. Server Discovery (derive dari flows ke port server well-known)
|
// 39. Server Discovery (derive dari flows ke port server well-known)
|
||||||
const srvDisc = await netify.fetchServerDiscovery(100);
|
const srvDisc = await netify.fetchServerDiscovery(100);
|
||||||
if (srvDisc?.length) { db.insertServerDiscovery(srvDisc, fetchedAt); console.log(`[Scheduler] OK SrvDisc : ${srvDisc.length} baris`); }
|
if (srvDisc?.length) { db.insertServerDiscovery(srvDisc, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK SrvDisc : ${srvDisc.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- SrvDisc : tidak ada data`);
|
else console.log(`[Scheduler] -- SrvDisc : tidak ada data`);
|
||||||
|
|
||||||
// 40. Tor Detection (derive dari apps/hostnames mengandung "tor")
|
// 40. Tor Detection (derive dari apps/hostnames mengandung "tor")
|
||||||
const torDet = await netify.fetchTorDetection(50);
|
const torDet = await netify.fetchTorDetection(50);
|
||||||
if (torDet?.length) { db.insertTorDetection(torDet, fetchedAt); console.log(`[Scheduler] OK TorDet : ${torDet.length} baris`); }
|
if (torDet?.length) { db.insertTorDetection(torDet, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK TorDet : ${torDet.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- TorDet : tidak ada data`);
|
else console.log(`[Scheduler] -- TorDet : tidak ada data`);
|
||||||
|
|
||||||
// 41. Unencrypted Password (derive dari flows ke port cleartext auth)
|
// 41. Unencrypted Password (derive dari flows ke port cleartext auth)
|
||||||
const unencPwd = await netify.fetchUnencryptedPasswords(50);
|
const unencPwd = await netify.fetchUnencryptedPasswords(50);
|
||||||
if (unencPwd?.length) { db.insertUnencryptedPasswords(unencPwd, fetchedAt); console.log(`[Scheduler] OK UnencPwd : ${unencPwd.length} baris`); }
|
if (unencPwd?.length) { db.insertUnencryptedPasswords(unencPwd, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK UnencPwd : ${unencPwd.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- UnencPwd : tidak ada data`);
|
else console.log(`[Scheduler] -- UnencPwd : tidak ada data`);
|
||||||
|
|
||||||
// 42. VPN Detection (derive dari apps/protocols/ports VPN)
|
// 42. VPN Detection (derive dari apps/protocols/ports VPN)
|
||||||
const vpnDet = await netify.fetchVPNDetection(50);
|
const vpnDet = await netify.fetchVPNDetection(50);
|
||||||
if (vpnDet?.length) { db.insertVPNDetection(vpnDet, fetchedAt); console.log(`[Scheduler] OK VPNDet : ${vpnDet.length} baris`); }
|
if (vpnDet?.length) { db.insertVPNDetection(vpnDet, fetchedAt, SITE_UUID); console.log(`[Scheduler] OK VPNDet : ${vpnDet.length} baris`); }
|
||||||
else console.log(`[Scheduler] -- VPNDet : tidak ada data`);
|
else console.log(`[Scheduler] -- VPNDet : tidak ada data`);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+21
-2
@@ -3,17 +3,36 @@ const path = require('path');
|
|||||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
|
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
|
const cookieParser = require('cookie-parser');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.BACKEND_PORT || 3001;
|
const PORT = process.env.BACKEND_PORT || 3001;
|
||||||
|
|
||||||
// ── Middleware ────────────────────────────────────────────────────────────────
|
// ── Middleware ────────────────────────────────────────────────────────────────
|
||||||
app.use(cors());
|
app.use(cors({ origin: true, credentials: true }));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
// ── API Routes ────────────────────────────────────────────────────────────────
|
// ── API Routes ────────────────────────────────────────────────────────────────
|
||||||
|
const authRoutes = require('./routes/auth');
|
||||||
|
app.use('/api/auth', authRoutes);
|
||||||
|
|
||||||
|
// Auth Middleware for Dashboard
|
||||||
|
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);
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
res.status(401).json({ error: 'Invalid token' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const dashboardRoutes = require('./routes/dashboard');
|
const dashboardRoutes = require('./routes/dashboard');
|
||||||
app.use('/api/dashboard', dashboardRoutes);
|
app.use('/api/dashboard', requireAuth, dashboardRoutes);
|
||||||
|
|
||||||
// ── Health Check ──────────────────────────────────────────────────────────────
|
// ── Health Check ──────────────────────────────────────────────────────────────
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
|
|||||||
Whitespace-only changes.
Generated
+201
-114
@@ -9,13 +9,17 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.18.1",
|
"axios": "^1.18.1",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
"better-sqlite3": "^12.11.1",
|
"better-sqlite3": "^12.11.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^10.0.3",
|
"concurrently": "^10.0.3",
|
||||||
|
"cookie": "^2.0.1",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"d3-scale": "^4.0.2",
|
"d3-scale": "^4.0.2",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
"lucide-react": "^1.21.0",
|
"lucide-react": "^1.21.0",
|
||||||
"next": "16.2.9",
|
"next": "16.2.9",
|
||||||
"node-cron": "^4.5.0",
|
"node-cron": "^4.5.0",
|
||||||
@@ -28,7 +32,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/d3-scale": "^4.0.9",
|
"@types/d3-scale": "^4.0.9",
|
||||||
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
@@ -628,9 +635,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -647,9 +651,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -666,9 +667,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -685,9 +683,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -704,9 +699,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -723,9 +715,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -742,9 +731,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -761,9 +747,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -780,9 +763,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -805,9 +785,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -830,9 +807,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -855,9 +829,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -880,9 +851,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -905,9 +873,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -930,9 +895,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -955,9 +917,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1173,9 +1132,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1192,9 +1148,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1211,9 +1164,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1230,9 +1180,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1519,9 +1466,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1539,9 +1483,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1559,9 +1500,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1579,9 +1517,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1680,6 +1615,20 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/bcryptjs": {
|
||||||
|
"version": "2.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
|
||||||
|
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/d3-array": {
|
"node_modules/@types/d3-array": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||||
@@ -1816,6 +1765,24 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jsonwebtoken": {
|
||||||
|
"version": "9.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
|
||||||
|
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/ms": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/ms": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.43",
|
"version": "20.19.43",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||||
@@ -2266,9 +2233,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2283,9 +2247,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2300,9 +2261,6 @@
|
|||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2317,9 +2275,6 @@
|
|||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2334,9 +2289,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2351,9 +2303,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2368,9 +2317,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2385,9 +2331,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2402,9 +2345,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2419,9 +2359,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2894,6 +2831,15 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bcryptjs": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"bin": {
|
||||||
|
"bcrypt": "bin/bcrypt"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/better-sqlite3": {
|
"node_modules/better-sqlite3": {
|
||||||
"version": "12.11.1",
|
"version": "12.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
|
||||||
@@ -3047,6 +2993,12 @@
|
|||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.1.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-equal-constant-time": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -3309,6 +3261,32 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-parser": {
|
||||||
|
"version": "1.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||||
|
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.7.2",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-parser/node_modules/cookie": {
|
||||||
"version": "0.7.2",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
@@ -3317,6 +3295,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie-parser/node_modules/cookie-signature": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cookie-signature": {
|
"node_modules/cookie-signature": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||||
@@ -3829,6 +3813,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -4587,6 +4580,15 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express/node_modules/cookie": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -5881,6 +5883,40 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonwebtoken": {
|
||||||
|
"version": "9.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
|
||||||
|
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jws": "^4.0.1",
|
||||||
|
"lodash.includes": "^4.3.0",
|
||||||
|
"lodash.isboolean": "^3.0.3",
|
||||||
|
"lodash.isinteger": "^4.0.4",
|
||||||
|
"lodash.isnumber": "^3.0.3",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.isstring": "^4.0.1",
|
||||||
|
"lodash.once": "^4.0.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"semver": "^7.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsonwebtoken/node_modules/semver": {
|
||||||
|
"version": "7.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||||
|
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsx-ast-utils": {
|
"node_modules/jsx-ast-utils": {
|
||||||
"version": "3.3.5",
|
"version": "3.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||||
@@ -5897,6 +5933,27 @@
|
|||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jwa": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-equal-constant-time": "^1.0.1",
|
||||||
|
"ecdsa-sig-formatter": "1.0.11",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jws": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jwa": "^2.0.1",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@@ -6084,9 +6141,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6108,9 +6162,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6132,9 +6183,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6156,9 +6204,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6230,6 +6275,42 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.includes": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isboolean": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isinteger": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isnumber": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isplainobject": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isstring": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@@ -6237,6 +6318,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.once": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/loose-envify": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
|||||||
@@ -11,13 +11,17 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.18.1",
|
"axios": "^1.18.1",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
"better-sqlite3": "^12.11.1",
|
"better-sqlite3": "^12.11.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^10.0.3",
|
"concurrently": "^10.0.3",
|
||||||
|
"cookie": "^2.0.1",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"d3-scale": "^4.0.2",
|
"d3-scale": "^4.0.2",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
"lucide-react": "^1.21.0",
|
"lucide-react": "^1.21.0",
|
||||||
"next": "16.2.9",
|
"next": "16.2.9",
|
||||||
"node-cron": "^4.5.0",
|
"node-cron": "^4.5.0",
|
||||||
@@ -30,7 +34,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/d3-scale": "^4.0.9",
|
"@types/d3-scale": "^4.0.9",
|
||||||
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#f00a38" />
|
||||||
|
<stop offset="50%" stop-color="#7a0799" />
|
||||||
|
<stop offset="100%" stop-color="#0400ff" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Gradient Circle Background -->
|
||||||
|
<circle cx="100" cy="100" r="100" fill="url(#bgGradient)" />
|
||||||
|
|
||||||
|
<!-- White Abstract Shape (BackOne Logo) -->
|
||||||
|
<path d="M 50 45
|
||||||
|
L 90 45
|
||||||
|
C 145 45, 165 65, 165 105
|
||||||
|
C 165 155, 110 180, 55 180
|
||||||
|
C 115 160, 125 100, 50 95
|
||||||
|
Z"
|
||||||
|
fill="#FFFFFF" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 701 B |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,9 @@
|
|||||||
|
import { DashboardLayout } from "@/components/layout/DashboardLayout";
|
||||||
|
|
||||||
|
export default function AppLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return <DashboardLayout>{children}</DashboardLayout>;
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -22,22 +22,37 @@ export async function PATCH(req: NextRequest, props: { params: Promise<{ route:
|
|||||||
|
|
||||||
async function handleProxy(req: NextRequest, { params }: { params: Promise<{ route: string[] }> }) {
|
async function handleProxy(req: NextRequest, { params }: { params: Promise<{ route: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const route = (await params).route.join('/');
|
const routeArray = (await params).route;
|
||||||
const baseUrl = process.env.NETIFY_BASE_URL || 'https://manager.netify.ai/api/v1';
|
const route = routeArray.join('/');
|
||||||
|
|
||||||
// Construct the destination URL including search parameters
|
let destinationUrl = '';
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
const destinationUrl = `${baseUrl}/${route}${url.search}`;
|
|
||||||
|
if (routeArray[0] === 'auth' || routeArray[0] === 'dashboard') {
|
||||||
|
const localBase = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:3001';
|
||||||
|
destinationUrl = `${localBase}/api/${route}${url.search}`;
|
||||||
|
} else {
|
||||||
|
const baseUrl = process.env.NETIFY_BASE_URL || 'https://manager.netify.ai/api/v1';
|
||||||
|
destinationUrl = `${baseUrl}/${route}${url.search}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare headers
|
// Prepare headers
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.set('Content-Type', 'application/json');
|
headers.set('Content-Type', 'application/json');
|
||||||
|
|
||||||
if (process.env.NETIFY_JWT_TOKEN && process.env.NETIFY_SITE_UUID) {
|
if (routeArray[0] !== 'auth' && routeArray[0] !== 'dashboard') {
|
||||||
headers.set('x-api-key', process.env.NETIFY_JWT_TOKEN);
|
if (process.env.NETIFY_JWT_TOKEN && process.env.NETIFY_SITE_UUID) {
|
||||||
headers.set('x-net-site', process.env.NETIFY_SITE_UUID);
|
headers.set('x-api-key', process.env.NETIFY_JWT_TOKEN);
|
||||||
} else if (process.env.NETIFY_API_KEY) {
|
headers.set('x-net-site', process.env.NETIFY_SITE_UUID);
|
||||||
headers.set('x-api-key', process.env.NETIFY_API_KEY);
|
} else if (process.env.NETIFY_API_KEY) {
|
||||||
|
headers.set('x-api-key', process.env.NETIFY_API_KEY);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For local auth/dashboard routes, pass along the cookie for authentication
|
||||||
|
const cookie = req.headers.get('cookie');
|
||||||
|
if (cookie) {
|
||||||
|
headers.set('cookie', cookie);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some requests like GET cannot have a body
|
// Some requests like GET cannot have a body
|
||||||
|
|||||||
+4
-5
@@ -1,16 +1,14 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { DashboardLayout } from "@/components/layout/DashboardLayout";
|
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
variable: "--font-inter",
|
variable: "--font-inter",
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Netify Dashboard",
|
title: "BackOne Dashboard",
|
||||||
description: "Netify Network Intelligence Dashboard",
|
description: "BackOne by PT. Data Bisnis Solusi - Network Intelligence Dashboard",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -22,9 +20,10 @@ export default function RootLayout({
|
|||||||
<html
|
<html
|
||||||
lang="en"
|
lang="en"
|
||||||
className={`${inter.variable} h-full antialiased dark`}
|
className={`${inter.variable} h-full antialiased dark`}
|
||||||
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
<body className="min-h-full flex flex-col">
|
<body className="min-h-full flex flex-col">
|
||||||
<DashboardLayout>{children}</DashboardLayout>
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { Lock, User } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError("");
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/auth/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(data.error || "Login failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push("/");
|
||||||
|
router.refresh(); // Refresh to update middleware state
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background flex flex-col justify-center items-center relative overflow-hidden">
|
||||||
|
{/* Background Glows */}
|
||||||
|
<div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] rounded-full bg-red-600/20 blur-[120px] pointer-events-none" />
|
||||||
|
<div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] rounded-full bg-blue-700/20 blur-[120px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="z-10 w-full max-w-md p-8 bg-sidebar/80 backdrop-blur-2xl rounded-2xl border border-border/50 shadow-2xl relative">
|
||||||
|
<div className="flex flex-col items-center mb-8">
|
||||||
|
<img src="/backone-logo.svg" alt="BackOne" className="w-16 h-16 drop-shadow-[0_0_15px_rgba(220,38,38,0.5)] mb-4" />
|
||||||
|
<h1 className="text-2xl font-bold text-white tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white to-white/70">
|
||||||
|
BackOne Dashboard
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-muted-foreground mt-2">Sign in to your account</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mb-6 p-3 rounded-lg bg-red-500/10 border border-red-500/50 text-red-500 text-sm text-center">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleLogin} className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-slate-300">Username</label>
|
||||||
|
<div className="relative">
|
||||||
|
<User className="absolute left-3 top-2.5 h-5 w-5 text-muted-foreground" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
className="w-full pl-10 pr-4 py-2.5 bg-background border border-border rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary/50 transition-all"
|
||||||
|
placeholder="admin"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-slate-300">Password</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="absolute left-3 top-2.5 h-5 w-5 text-muted-foreground" />
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="w-full pl-10 pr-4 py-2.5 bg-background border border-border rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary/50 transition-all"
|
||||||
|
placeholder="••••••••"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full py-2.5 px-4 bg-gradient-to-r from-red-600 to-blue-700 hover:from-red-500 hover:to-blue-600 text-white font-medium rounded-lg shadow-[0_0_15px_rgba(220,38,38,0.3)] hover:shadow-[0_0_20px_rgba(220,38,38,0.5)] transition-all duration-300 flex items-center justify-center mt-6"
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||||
|
) : (
|
||||||
|
"Sign In"
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,10 +12,11 @@ import {
|
|||||||
Zap,
|
Zap,
|
||||||
Radio,
|
Radio,
|
||||||
Lock,
|
Lock,
|
||||||
Search
|
Search,
|
||||||
|
LogOut
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{ name: "Summary", href: "/", icon: LayoutDashboard },
|
{ name: "Summary", href: "/", icon: LayoutDashboard },
|
||||||
@@ -37,6 +38,17 @@ const navigation = [
|
|||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await fetch('/api/auth/logout', { method: 'POST' });
|
||||||
|
router.push('/login');
|
||||||
|
router.refresh();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-64 flex-col border-r border-border/50 bg-sidebar/80 backdrop-blur-2xl relative">
|
<div className="flex h-full w-64 flex-col border-r border-border/50 bg-sidebar/80 backdrop-blur-2xl relative">
|
||||||
@@ -44,11 +56,9 @@ export function Sidebar() {
|
|||||||
<div className="absolute inset-0 bg-gradient-to-b from-primary/5 via-transparent to-transparent pointer-events-none" />
|
<div className="absolute inset-0 bg-gradient-to-b from-primary/5 via-transparent to-transparent pointer-events-none" />
|
||||||
|
|
||||||
<div className="flex h-[72px] shrink-0 items-center px-6 border-b border-border/50 relative z-10">
|
<div className="flex h-[72px] shrink-0 items-center px-6 border-b border-border/50 relative z-10">
|
||||||
<div className="relative flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 border border-primary/20 mr-3 shadow-[0_0_15px_rgba(59,130,246,0.2)]">
|
<img src="/backone-logo.svg" alt="BackOne Logo" className="w-8 h-8 mr-3 rounded-full drop-shadow-[0_0_10px_rgba(255,0,0,0.3)]" />
|
||||||
<Shield className="h-5 w-5 text-primary" />
|
|
||||||
</div>
|
|
||||||
<span className="text-lg font-bold text-white tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white to-white/70">
|
<span className="text-lg font-bold text-white tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white to-white/70">
|
||||||
Netify
|
BackOne
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col overflow-y-auto relative z-10">
|
<div className="flex flex-1 flex-col overflow-y-auto relative z-10">
|
||||||
@@ -91,6 +101,16 @@ export function Sidebar() {
|
|||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 border-t border-border/50 relative z-10 shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="flex w-full items-center rounded-lg px-3 py-2.5 text-sm font-medium text-red-400 hover:bg-red-500/10 hover:text-red-300 transition-all duration-200"
|
||||||
|
>
|
||||||
|
<LogOut className="mr-3 h-5 w-5" />
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
const token = request.cookies.get('token')?.value;
|
||||||
|
const { pathname } = request.nextUrl;
|
||||||
|
|
||||||
|
const isAuthPage = pathname.startsWith('/login');
|
||||||
|
|
||||||
|
// Protect all pages except /login and static assets/api
|
||||||
|
// The actual API routes inside /api are protected by Express backend (or Next.js API routes)
|
||||||
|
// We don't intercept /api requests here to avoid breaking them, but we protect the UI pages
|
||||||
|
|
||||||
|
if (pathname.startsWith('/api') || pathname.startsWith('/_next') || pathname === '/favicon.ico' || pathname.endsWith('.svg')) {
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token && !isAuthPage) {
|
||||||
|
const loginUrl = new URL('/login', request.url);
|
||||||
|
return NextResponse.redirect(loginUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token && isAuthPage) {
|
||||||
|
const dashboardUrl = new URL('/', request.url);
|
||||||
|
return NextResponse.redirect(dashboardUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
||||||
|
};
|
||||||
Reference in new issue
Block a user