feat: complete multi-user RBAC integration, login system, and fix backend API crashing

This commit is contained in:
Rafif-Riqullah-Siregar committed 2026-07-01 11:47:17 +07:00
1 parent 4aa12511e8
commit 72ded25e4d
31 files changed
+954 -411

No files matched your search

+13
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+64
View File
@@ -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
View File
@@ -6,8 +6,8 @@ const { runPoll } = require('../scheduler');
// GET /api/dashboard/summary — kartu ringkasan (top of page)
router.get('/summary', (req, res) => {
const stats = db.getStats();
const timeline = db.getBandwidthTimeline(1);
const stats = db.getStats(req.user?.site_uuid);
const timeline = db.getBandwidthTimeline(1, req.user?.site_uuid);
const latest = timeline[0] ?? {};
res.json({
@@ -27,54 +27,54 @@ router.get('/summary', (req, res) => {
// GET /api/dashboard/apps — top aplikasi
router.get('/apps', (req, res) => {
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 });
});
// GET /api/dashboard/devices — daftar device
router.get('/devices', (req, res) => {
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 });
});
// GET /api/dashboard/flows — flow aktif
router.get('/flows', (req, res) => {
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 });
});
// GET /api/dashboard/threats — ancaman keamanan
router.get('/threats', (req, res) => {
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 });
});
// GET /api/dashboard/protocols — top protokol
router.get('/protocols', (req, res) => {
const data = db.getLatestProtocols();
const data = db.getLatestProtocols(20, req.user?.site_uuid);
res.json({ ok: true, data });
});
// GET /api/dashboard/countries — top negara
router.get('/countries', (req, res) => {
const data = db.getLatestCountries();
const data = db.getLatestCountries(15, req.user?.site_uuid);
res.json({ ok: true, data });
});
// GET /api/dashboard/dns — top DNS queries
router.get('/dns', (req, res) => {
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 });
});
// GET /api/dashboard/events — events terbaru
router.get('/events', (req, res) => {
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 => ({
id: r.id,
event_id: r.event_id,
@@ -90,7 +90,7 @@ router.get('/events', (req, res) => {
// GET /api/dashboard/timeline — bandwidth timeline (grafik)
router.get('/timeline', (req, res) => {
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 });
});
@@ -102,67 +102,67 @@ router.post('/refresh', async (req, res) => {
// ─── ROUTES FITUR BARU 1-11 ───────────────────────────────────────────────────
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) => {
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) => {
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) => {
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) => {
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) => {
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) => {
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) => {
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) => {
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) => {
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) => {
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 ──────────────────────────────────────
// TLS Versions
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
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
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)
router.get('/netbios', (req, res) => {
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)
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 ─────────────────────────────────────────────────────────
@@ -170,110 +170,110 @@ router.get('/discovery-os', (req, res) => {
// 12. DHCP Class Fingerprint
router.get('/dhcp-fingerprints', (req, res) => {
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
router.get('/http-user-agents', (req, res) => {
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
router.get('/sni-hostnames', (req, res) => {
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
router.get('/ssl-server-cn', (req, res) => {
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
router.get('/quic-hostnames', (req, res) => {
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
router.get('/bittorrent-hashes', (req, res) => {
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
router.get('/ssh-versions', (req, res) => {
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.)
router.get('/mdns-hostnames', (req, res) => {
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 ────────────────────────────────────────────────
// Stats ringkasan semua intelligence (untuk badge count di tab)
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
router.get('/intelligence/crypto-mining', (req, res) => {
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
router.get('/intelligence/device-discovery', (req, res) => {
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
router.get('/intelligence/encryption-audit', (req, res) => {
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
router.get('/intelligence/insecure-protocols', (req, res) => {
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
router.get('/intelligence/ip-reputation', (req, res) => {
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
router.get('/intelligence/server-discovery', (req, res) => {
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
router.get('/intelligence/tor', (req, res) => {
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
router.get('/intelligence/unencrypted-passwords', (req, res) => {
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
router.get('/intelligence/vpn', (req, res) => {
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 ────────────────────────────────────────────────────────────
+44 -43
View File
@@ -2,6 +2,7 @@
const cron = require('node-cron');
const netify = require('./netify');
const db = require('./database');
const SITE_UUID = process.env.NETIFY_SITE_UUID || 'dummy_site_uuid';
let isRunning = false;
@@ -18,7 +19,7 @@ async function runPoll() {
// 1. Top Aplikasi
const apps = await netify.fetchTopApps(1440, 20);
if (apps && Array.isArray(apps)) {
db.insertBandwidthApps(apps, fetchedAt);
db.insertBandwidthApps(apps, fetchedAt, SITE_UUID);
console.log(`[Scheduler] OK Apps : ${apps.length} baris`);
} else {
console.log(`[Scheduler] -- Apps : tidak ada data`);
@@ -27,7 +28,7 @@ async function runPoll() {
// 2. Top Devices — pakai fetchDiscoveredDevices yg sudah dinormalisasi
const devices = await netify.fetchDiscoveredDevices(1440, 200);
if (devices && Array.isArray(devices)) {
db.insertDevices(devices, fetchedAt);
db.insertDevices(devices, fetchedAt, SITE_UUID);
console.log(`[Scheduler] OK Devices : ${devices.length} baris`);
} else {
console.log(`[Scheduler] -- Devices : tidak ada data`);
@@ -36,7 +37,7 @@ async function runPoll() {
// 3. Top Protokol
const protocols = await netify.fetchTopProtocols(1440, 20);
if (protocols && Array.isArray(protocols)) {
db.insertProtocols(protocols, fetchedAt);
db.insertProtocols(protocols, fetchedAt, SITE_UUID);
console.log(`[Scheduler] OK Protocols : ${protocols.length} baris`);
} else {
console.log(`[Scheduler] -- Protocols : tidak ada data`);
@@ -45,7 +46,7 @@ async function runPoll() {
// 4. Top Negara
const countries = await netify.fetchTopCountries(1440, 15);
if (countries && Array.isArray(countries)) {
db.insertCountries(countries, fetchedAt);
db.insertCountries(countries, fetchedAt, SITE_UUID);
console.log(`[Scheduler] OK Countries : ${countries.length} baris`);
} else {
console.log(`[Scheduler] -- Countries : tidak ada data`);
@@ -54,7 +55,7 @@ async function runPoll() {
// 5. Top Domain/DNS
const domains = await netify.fetchTopDomains(1440, 20);
if (domains && Array.isArray(domains)) {
db.insertDNS(domains, fetchedAt);
db.insertDNS(domains, fetchedAt, SITE_UUID);
console.log(`[Scheduler] OK DNS : ${domains.length} baris`);
} else {
console.log(`[Scheduler] -- DNS : tidak ada data`);
@@ -63,7 +64,7 @@ async function runPoll() {
// 6. Flows — pakai local_ip sebagai proxy
const flows = await netify.fetchFlows(200);
if (flows && Array.isArray(flows)) {
db.insertFlows(flows, fetchedAt);
db.insertFlows(flows, fetchedAt, SITE_UUID);
console.log(`[Scheduler] OK Flows : ${flows.length} baris`);
} else {
console.log(`[Scheduler] -- Flows : tidak ada data`);
@@ -72,7 +73,7 @@ async function runPoll() {
// 7. Threats — dari Events Status
const threats = await netify.fetchCyberThreats(1440, 50);
if (threats && Array.isArray(threats)) {
db.insertThreats(threats, fetchedAt);
db.insertThreats(threats, fetchedAt, SITE_UUID);
console.log(`[Scheduler] OK Threats : ${threats.length} baris`);
} else {
console.log(`[Scheduler] -- Threats : tidak ada data`);
@@ -81,7 +82,7 @@ async function runPoll() {
// 8. Events Log
const events = await netify.fetchEvents(50);
if (events && Array.isArray(events)) {
db.insertEvents(events, fetchedAt);
db.insertEvents(events, fetchedAt, SITE_UUID);
console.log(`[Scheduler] OK Events : ${events.length} baris`);
} else {
console.log(`[Scheduler] -- Events : tidak ada data`);
@@ -89,180 +90,180 @@ async function runPoll() {
// 10. App Categories
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`);
// 11. Continents
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`);
// 12. Regions
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`);
// 13. Cities
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`);
// 14. VLANs
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`);
// 15. Interfaces
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`);
// 16. Flow Types
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`);
// 17. Flow Origins
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`);
// 18. IP Versions
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`);
// 19. Remote IPs
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`);
// 20. MAC Bandwidth
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`);
// 9. Bandwidth Timeline
const summary = await netify.fetchBandwidthSummary(1440);
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`);
// 21. TLS Versions
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`);
// 22. TLS Ciphers
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`);
// 23. TLS Security
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`);
// 24. NetBIOS Hostnames
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`);
// 25. Discovery OS (standalone — OS yang terdeteksi di jaringan)
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`);
// 26. DHCP Class Fingerprint
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`);
// 27. HTTP User-Agent
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`);
// 28. HTTPS SNI Hostname
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`);
// 29. SSL Server Common Name
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`);
// 30. QUIC Hostname
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`);
// 31. BitTorrent Info Hash
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`);
// 32. SSH Client (field: ssh_client)
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`);
// 32b. SSH Server (field: ssh_server)
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`);
// 33. mDNS Hostname (Chromecast, Apple TV, etc.)
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`);
// ─── INTELLIGENCE 22-30 (derive dari data yang tersedia) ─────────────────
// 34. Cryptocurrency Mining (derive dari apps + flows ke port mining)
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`);
// 35. Device Discovery (derive dari flows + bandwidth per-IP)
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`);
// 36. Encryption Audit (derive dari flows per-IP: port encrypted vs plain)
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`);
// 37. Insecure Protocols (derive dari top protocols)
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`);
// 38. IP Reputation (derive dari top remote_ip + high-risk countries)
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`);
// 39. Server Discovery (derive dari flows ke port server well-known)
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`);
// 40. Tor Detection (derive dari apps/hostnames mengandung "tor")
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`);
// 41. Unencrypted Password (derive dari flows ke port cleartext auth)
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`);
// 42. VPN Detection (derive dari apps/protocols/ports VPN)
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`);
} catch (err) {
+21 -2
View File
@@ -3,17 +3,36 @@ const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const app = express();
const PORT = process.env.BACKEND_PORT || 3001;
// ── Middleware ────────────────────────────────────────────────────────────────
app.use(cors());
app.use(cors({ origin: true, credentials: true }));
app.use(express.json());
app.use(cookieParser());
// ── 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');
app.use('/api/dashboard', dashboardRoutes);
app.use('/api/dashboard', requireAuth, dashboardRoutes);
// ── Health Check ──────────────────────────────────────────────────────────────
app.get('/api/health', (req, res) => {
View File
Whitespace-only changes.
+201 -114
View File
@@ -9,13 +9,17 @@
"version": "0.1.0",
"dependencies": {
"axios": "^1.18.1",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.11.1",
"clsx": "^2.1.1",
"concurrently": "^10.0.3",
"cookie": "^2.0.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"d3-scale": "^4.0.2",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"jsonwebtoken": "^9.0.3",
"lucide-react": "^1.21.0",
"next": "16.2.9",
"node-cron": "^4.5.0",
@@ -28,7 +32,10 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/bcryptjs": "^2.4.6",
"@types/cookie": "^0.6.0",
"@types/d3-scale": "^4.0.9",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
@@ -628,9 +635,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -647,9 +651,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -666,9 +667,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -685,9 +683,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -704,9 +699,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -723,9 +715,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -742,9 +731,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -761,9 +747,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -780,9 +763,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -805,9 +785,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -830,9 +807,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -855,9 +829,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -880,9 +851,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -905,9 +873,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -930,9 +895,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -955,9 +917,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -1173,9 +1132,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1192,9 +1148,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1211,9 +1164,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1230,9 +1180,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1519,9 +1466,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1539,9 +1483,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1559,9 +1500,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1579,9 +1517,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1680,6 +1615,20 @@
"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": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
@@ -1816,6 +1765,24 @@
"dev": true,
"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": {
"version": "20.19.43",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
@@ -2266,9 +2233,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2283,9 +2247,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2300,9 +2261,6 @@
"loong64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2317,9 +2275,6 @@
"loong64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2334,9 +2289,6 @@
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2351,9 +2303,6 @@
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2368,9 +2317,6 @@
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2385,9 +2331,6 @@
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2402,9 +2345,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2419,9 +2359,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2894,6 +2831,15 @@
"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": {
"version": "12.11.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
@@ -3047,6 +2993,12 @@
"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": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -3309,6 +3261,32 @@
"license": "MIT"
},
"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",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
@@ -3317,6 +3295,12 @@
"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": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
@@ -3829,6 +3813,15 @@
"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": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -4587,6 +4580,15 @@
"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": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5881,6 +5883,40 @@
"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": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -5897,6 +5933,27 @@
"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": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -6084,9 +6141,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6108,9 +6162,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6132,9 +6183,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6156,9 +6204,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6230,6 +6275,42 @@
"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": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -6237,6 +6318,12 @@
"dev": true,
"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": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+7
View File
@@ -11,13 +11,17 @@
},
"dependencies": {
"axios": "^1.18.1",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.11.1",
"clsx": "^2.1.1",
"concurrently": "^10.0.3",
"cookie": "^2.0.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"d3-scale": "^4.0.2",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"jsonwebtoken": "^9.0.3",
"lucide-react": "^1.21.0",
"next": "16.2.9",
"node-cron": "^4.5.0",
@@ -30,7 +34,10 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/bcryptjs": "^2.4.6",
"@types/cookie": "^0.6.0",
"@types/d3-scale": "^4.0.9",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
+21
View File
@@ -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.
+9
View File
@@ -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.
+24 -9
View File
@@ -22,22 +22,37 @@ export async function PATCH(req: NextRequest, props: { params: Promise<{ route:
async function handleProxy(req: NextRequest, { params }: { params: Promise<{ route: string[] }> }) {
try {
const route = (await params).route.join('/');
const baseUrl = process.env.NETIFY_BASE_URL || 'https://manager.netify.ai/api/v1';
const routeArray = (await params).route;
const route = routeArray.join('/');
// Construct the destination URL including search parameters
let destinationUrl = '';
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
const headers = new Headers();
headers.set('Content-Type', 'application/json');
if (process.env.NETIFY_JWT_TOKEN && process.env.NETIFY_SITE_UUID) {
headers.set('x-api-key', process.env.NETIFY_JWT_TOKEN);
headers.set('x-net-site', process.env.NETIFY_SITE_UUID);
} else if (process.env.NETIFY_API_KEY) {
headers.set('x-api-key', process.env.NETIFY_API_KEY);
if (routeArray[0] !== 'auth' && routeArray[0] !== 'dashboard') {
if (process.env.NETIFY_JWT_TOKEN && process.env.NETIFY_SITE_UUID) {
headers.set('x-api-key', process.env.NETIFY_JWT_TOKEN);
headers.set('x-net-site', process.env.NETIFY_SITE_UUID);
} 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
+4 -5
View File
@@ -1,16 +1,14 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { DashboardLayout } from "@/components/layout/DashboardLayout";
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Netify Dashboard",
description: "Netify Network Intelligence Dashboard",
title: "BackOne Dashboard",
description: "BackOne by PT. Data Bisnis Solusi - Network Intelligence Dashboard",
};
export default function RootLayout({
@@ -22,9 +20,10 @@ export default function RootLayout({
<html
lang="en"
className={`${inter.variable} h-full antialiased dark`}
suppressHydrationWarning
>
<body className="min-h-full flex flex-col">
<DashboardLayout>{children}</DashboardLayout>
{children}
</body>
</html>
);
+108
View File
@@ -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>
);
}
+26 -6
View File
@@ -12,10 +12,11 @@ import {
Zap,
Radio,
Lock,
Search
Search,
LogOut
} from "lucide-react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
const navigation = [
{ name: "Summary", href: "/", icon: LayoutDashboard },
@@ -37,6 +38,17 @@ const navigation = [
export function Sidebar() {
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 (
<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="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)]">
<Shield className="h-5 w-5 text-primary" />
</div>
<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)]" />
<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>
</div>
<div className="flex flex-1 flex-col overflow-y-auto relative z-10">
@@ -91,6 +101,16 @@ export function Sidebar() {
})}
</nav>
</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>
);
}
+33
View File
@@ -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).*)'],
};