history) {
- String path = alert.getAttemptedPath().toLowerCase();
- return MALICIOUS_PATTERNS.stream().anyMatch(path::contains);
+ String payload =
+ String.join(
+ " ",
+ nullSafe(alert.getAttemptedPath()),
+ nullSafe(alert.getReason()),
+ nullSafe(alert.getMethod()))
+ .toLowerCase();
+ boolean alertMatch = MALICIOUS_PATTERNS.stream().anyMatch(payload::contains);
+ if (alertMatch) {
+ return true;
+ }
+
+ if (history == null || history.isEmpty()) {
+ return false;
+ }
+
+ return history.stream()
+ .map(
+ log ->
+ (nullSafe(log.getPath())
+ + " "
+ + nullSafe(log.getQueryParams())
+ + " "
+ + nullSafe(log.getUserAgent()))
+ .toLowerCase())
+ .anyMatch(candidate -> MALICIOUS_PATTERNS.stream().anyMatch(candidate::contains));
}
@Override
@@ -41,4 +65,8 @@ public Duration getBlockDuration() {
public String getReason() {
return "CRITICAL_INJECTION_ATTEMPT";
}
+
+ private String nullSafe(String value) {
+ return value == null ? "" : value;
+ }
}
diff --git a/MCPService/src/main/resources/application.yml b/MCPService/src/main/resources/application.yml
index fbafa11..745ac85 100644
--- a/MCPService/src/main/resources/application.yml
+++ b/MCPService/src/main/resources/application.yml
@@ -28,7 +28,7 @@ spring:
properties:
spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
- spring.json.trusted.packages: "*"
+ spring.json.trusted.packages: "edu.pict.mcpservice.kafkaEvents"
spring.json.use.type.headers: true
spring.json.type.mapping: "edu.pict.apigateway.kafkaEvent.SecurityAlertEvent:edu.pict.mcpservice.kafkaEvents.SecurityAlertEvent,edu.pict.apigateway.kafkaEvent.LogEvent:edu.pict.mcpservice.kafkaEvents.LogEvent"
@@ -36,6 +36,13 @@ spring:
concurrency: 3
type: batch
ack-mode: batch
+ cloud:
+ openfeign:
+ client:
+ config:
+ default:
+ connectTimeout: 2000
+ readTimeout: 5000
eureka:
client:
diff --git a/SECURITY_FLAW_STATUS_MATRIX.md b/SECURITY_FLAW_STATUS_MATRIX.md
new file mode 100644
index 0000000..5ed9566
--- /dev/null
+++ b/SECURITY_FLAW_STATUS_MATRIX.md
@@ -0,0 +1,44 @@
+# SentientGate Security Flaw Status Matrix (31 Items)
+
+Date: 2026-04-04
+Legend: `Fixed` = implemented, `Partial` = improved but incomplete, `Pending` = not implemented yet.
+
+| ID | Flaw | Status | Evidence | Next Action |
+|---|---|---|---|---|
+| 1 | Private key committed | Fixed | `private_key.pem` deleted | Rotate key material in all deployed environments |
+| 2 | Hardcoded HMAC/JWT secrets | Fixed | `ApiGateway/src/main/resources/application.yml` now uses env vars | Enforce secret manager in non-local env |
+| 3 | CSRF disabled | Pending | `ApiGateway/src/main/java/edu/pict/apigateway/config/SecurityConfig.java` still disables CSRF | Implement explicit CSRF/session policy |
+| 4 | `permitAll()` on all routes | Partial | `JwtExtractionFilter` now protects paths, but `SecurityConfig` still has `anyExchange().permitAll()` | Move auth enforcement into Spring Security chain |
+| 5 | Kafka trusted packages `*` | Fixed | `MCPService` + `LogingService` `application.yml` now restricted | Add tests for deserializer hardening |
+| 6 | Hardcoded DB creds | Partial | `LogingService` uses env vars; compose still weak defaults pattern | Remove weak defaults and enforce required secrets |
+| 7 | UUID-only blacklist | Fixed | `BlacklistFilter` and `EnforcementService` now use UUID + IP keys | Add ops tooling for IP unblock workflows |
+| 8 | No IP spoofing protection | Partial | `IpService` now validates trusted proxy context | Add configurable trusted proxy CIDR chain |
+| 9 | Pattern match path-only | Partial | `PatternMatchStrategy` now checks broader fields | Add body/header inspection stage upstream |
+| 10 | Prompt injection risk | Partial | `AnomalyDetectionService` sanitizes route sensitivity | Add strict prompt template + allowlist guards |
+| 11 | gRPC plaintext | Pending | `MCPService application.yml` still `negotiation-type: plaintext` | Enable TLS/mTLS for gRPC |
+| 12 | No inter-service auth | Pending | No service auth layer present | Add mTLS/API-token/JWT between services |
+| 13 | `ddl-auto: update` risk | Partial | Now env-driven in `LogingService application.yml` | Set production default to `validate` + migrations |
+| 14 | Missing `.gitignore` protections | Partial | `.gitignore` significantly hardened | Finalize tracked-file policy for sensitive configs |
+| 15 | Redis fire-and-forget block writes | Partial | JSON serialization + error callback improved | Add reliable write/ack strategy and retries/bulkhead |
+| 16 | `assert` in production path | Fixed | Removed in `SentientGateFilter` flow | Add static analysis rule to block future assertions |
+| 17 | AI path mismatch Feign vs controller | Fixed | `AiServiceFeignClient` now calls `/ai-service/anomaly/analyze` | Add integration test for MCP->AI call |
+| 18 | `findFirst()` first-match only | Pending | Strategy arbitration still first-match | Introduce severity-based strategy arbitration |
+| 19 | Burst strategy evasion | Pending | Existing threshold logic unchanged | Redesign burst detection with sliding windows |
+| 20 | No request body size limits | Pending | No global request-size guards configured | Add gateway and service request-size limits |
+| 21 | Restrictive CORS | Pending | CORS still hardcoded localhost in `SecurityConfig` | Make CORS env/profile driven |
+| 22 | No Kafka DLQ | Pending | No DLQ/retry topic wiring | Add error handler + DLQ topic |
+| 23 | Unsanitized DB text storage | Pending | Raw text fields remain | Sanitize/encode before render + constrain fields |
+| 24 | Missing healthchecks in compose | Pending | Most services lack healthchecks | Add healthchecks for redis/kafka/services |
+| 25 | Log rotation missing | Pending | No rotation policy configured | Add rolling appender/container log limits |
+| 26 | `stratagies` typo package | Pending | Path remains unchanged | Rename package to `strategies` safely |
+| 27 | `LogingService` typo name | Pending | Module name/path still typo | Rename module and references |
+| 28 | Placeholder `SECURITY.md` | Pending | File still template-level | Write real disclosure/support policy |
+| 29 | `show-sql: true` in LoggingService | Pending | Still enabled in `LogingService application.yml` | Disable in production profile |
+| 30 | No compose network isolation | Pending | Default shared network only | Add segmented docker networks |
+| 31 | No graceful shutdown config | Pending | No explicit graceful shutdown tuning | Enable graceful shutdown + timeout settings |
+
+## Totals
+- Fixed: 6
+- Partial: 8
+- Pending: 17
+
diff --git a/SYSTEM_REMEDIATION_PLAN_SOLID.md b/SYSTEM_REMEDIATION_PLAN_SOLID.md
new file mode 100644
index 0000000..b1c955b
--- /dev/null
+++ b/SYSTEM_REMEDIATION_PLAN_SOLID.md
@@ -0,0 +1,230 @@
+# SentientGate Remediation Plan + SOLID Audit
+
+Date: 2026-04-04
+Scope: Security flaws, architectural flaws, and SOLID-driven refactor plan.
+
+## 1. Execution Plan (Phased)
+
+### Phase 0: Immediate Containment (Day 0)
+- Rotate and remove leaked key material:
+ - `/private_key.pem`
+ - hardcoded secrets in `ApiGateway/src/main/resources/application.yml`
+- Move secrets to environment variables for all services.
+- Strengthen `.gitignore` for `*.pem`, `.env*`, `target/`, `build/`, `.idea/`, `application*.yml.bak`.
+- Remove tracked secret backups like `ApiGateway/src/main/resources/application.yml.bak`.
+
+Definition of Done:
+- No secrets in tracked source.
+- App starts with env-based secrets in local/dev profiles.
+
+### Phase 1: Gateway Security Baseline (Day 1-2)
+- Replace `permitAll()` with endpoint-level authorization policy.
+- Rework CSRF policy:
+ - enable for cookie/session browser flows, or
+ - explicitly disable only for fully stateless token APIs with compensating controls.
+- Integrate trusted proxy/IP extraction via `IpService` inside `SentientGateFilter`.
+- Replace UUID-only blacklist checks with UUID + IP strategy.
+- Remove production `assert` checks and replace with explicit validation/guard clauses.
+
+Definition of Done:
+- Protected internal routes require auth.
+- IP extraction is deterministic and proxy-safe.
+- Blacklist cannot be bypassed by only rotating UUID cookie.
+
+### Phase 2: MCP Pipeline Performance + Safety (Day 2-4)
+- Add pre-check: skip analysis when already blocked in Redis.
+- Add dedup window per UUID/signature for repeated alerts.
+- Add short TTL cache for recent history fetches.
+- Split strategy execution:
+ - fast deterministic strategies on consumer thread
+ - slow AI strategy on async executor.
+- Add timeout + circuit breaker for gRPC and AI calls.
+- Restrict Kafka `trusted.packages` from `"*"` to explicit packages.
+
+Definition of Done:
+- Under burst traffic, consumer lag remains bounded.
+- AI unavailability does not stall Kafka consumers.
+
+### Phase 3: Service Integration Correctness (Day 4-5)
+- Fix AI Feign client name/path mismatch.
+- Implement gRPC server in LoggingService for history retrieval.
+- Add gRPC deadlines and fallback paths in MCP.
+
+Definition of Done:
+- History-dependent strategies function correctly in integration tests.
+- MCP handles LoggingService outage without thread starvation.
+
+### Phase 4: Logging/Data Hardening (Day 5-6)
+- Replace `parallelStream()` with `stream()` in ingestion mapping.
+- Move `ddl-auto` to safer production profile (`validate`/migration-driven).
+- Add DB indexes for dashboard query paths.
+- Add short-lived cache for dashboard aggregates.
+- Store block metadata as JSON, not `record.toString()`.
+
+Definition of Done:
+- Dashboard load no longer spikes DB repeatedly.
+- Block records are machine-readable and auditable.
+
+### Phase 5: Resilience Baseline (Day 6-7)
+- Add profile-level HA guidance for Redis/Kafka/Postgres/service replicas.
+- Keep dev-simple profile; add prod-safe profile defaults.
+
+Definition of Done:
+- Clear failover and scaling baseline exists for non-local environments.
+
+### Phase 6: Verification + Documentation (Day 7)
+- Add/expand unit + integration tests for all fixed flaw classes.
+- Create fix tracker with:
+ - flaw ID
+ - changed files
+ - tests
+ - verification commands
+ - residual risks.
+
+Definition of Done:
+- Each flaw has traceable fix evidence.
+
+---
+
+## 2. SOLID Violation Audit (Current Code)
+
+## S — Single Responsibility Principle (SRP)
+
+Break 1:
+- File: `ApiGateway/src/main/java/edu/pict/apigateway/filters/global/SentientGateFilter.java`
+- Problem:
+ - same class extracts request metadata, decides alert severity, builds two event models, and publishes to Kafka.
+- Fix:
+ - split into:
+ - `RequestContextExtractor`
+ - `LogEventFactory`
+ - `SecurityAlertFactory`
+ - `SecurityEventPublisher`
+
+Break 2:
+- File: `MCPService/src/main/java/edu/pict/mcpservice/service/McpAnalysisService.java`
+- Problem:
+ - orchestrates history fetch, transforms data models, executes strategies, and triggers enforcement.
+- Fix:
+ - split orchestration:
+ - `HistoryProvider`
+ - `StrategyEvaluator`
+ - `ThreatDecisionEngine`
+ - `EnforcementCoordinator`
+
+Break 3:
+- File: `LogingService/src/main/java/edu/pict/loggingservice/service/DashboardStatsService.java`
+- Problem:
+ - handles stats retrieval, error fallback policy, throughput calculation, and p99 logic in one class.
+- Fix:
+ - separate into repository-facing query service + metrics calculation utility + caching facade.
+
+## O — Open/Closed Principle (OCP)
+
+Break 1:
+- File: `MCPService/src/main/java/edu/pict/mcpservice/service/McpAnalysisService.java`
+- Problem:
+ - transformation from gRPC model to internal model is inlined in service flow; adding alternate history sources forces edits in same class.
+- Fix:
+ - introduce mapper interface and pluggable history source adapters.
+
+Break 2:
+- File: `MCPService/src/main/java/edu/pict/mcpservice/stratagies/blocking/PatternMatchStrategy.java`
+- Problem:
+ - static embedded malicious patterns mean updates require code changes/redeploy.
+- Fix:
+ - externalize signatures to config/rules provider and keep strategy closed for modification.
+
+## L — Liskov Substitution Principle (LSP)
+
+Break 1 (behavioral contract risk):
+- File: `MCPService/src/main/java/edu/pict/mcpservice/stratagies/blocking/AiAnomalyStrategy.java`
+- Problem:
+ - strategy call can block for long periods while other strategies are fast/pure. This violates expected substitutability of `ThreatStrategy` from a caller timing perspective.
+- Fix:
+ - split strategy contracts:
+ - `SynchronousThreatStrategy`
+ - `AsynchronousThreatStrategy`
+ - evaluate each in appropriate execution pipeline.
+
+## I — Interface Segregation Principle (ISP)
+
+Break 1:
+- File: `MCPService/src/main/java/edu/pict/mcpservice/stratagies/blocking/ThreatStrategy.java` (used by all strategies)
+- Problem:
+ - single contract forces both fast deterministic and slow remote-dependent strategies into same interface behavior.
+- Fix:
+ - segregate into narrower interfaces:
+ - `RuleBasedThreatStrategy`
+ - `AiThreatStrategy`
+ - optional marker for history requirement.
+
+## D — Dependency Inversion Principle (DIP)
+
+Break 1:
+- File: `MCPService/src/main/java/edu/pict/mcpservice/service/McpAnalysisService.java`
+- Problem:
+ - depends directly on concrete `EventHistoryService` and `EnforcementService`.
+- Fix:
+ - depend on abstractions:
+ - `HistoryProvider`
+ - `BlockEnforcer`.
+
+Break 2:
+- File: `MCPService/src/main/java/edu/pict/mcpservice/service/AIClient.java`
+- Problem:
+ - concrete dependence on Feign transport details leaks into domain service behavior.
+- Fix:
+ - introduce `AnomalyScoringPort` interface; Feign/WebClient become infrastructure adapters.
+
+Break 3:
+- File: `ApiGateway/src/main/java/edu/pict/apigateway/config/SecurityConfig.java`
+- Problem:
+ - security policy is hardcoded directly in config class (`anyExchange().permitAll()`), making higher-level policy dependent on low-level implementation.
+- Fix:
+ - model policy in dedicated `AuthorizationPolicy` abstraction and apply through config.
+
+---
+
+## 3. SOLID-Aligned Target Module Structure
+
+ApiGateway:
+- `security.policy` (authorization + CSRF decisions)
+- `request.context` (visitor/IP extraction and validation)
+- `events.factory` (log/alert event composition)
+- `events.publisher` (Kafka transport only)
+
+MCPService:
+- `analysis.core` (decision engine)
+- `analysis.strategy.sync` (fast local rules)
+- `analysis.strategy.async` (AI/remote rules)
+- `analysis.history` (grpc/cache providers)
+- `analysis.enforcement` (blocking port + redis adapter)
+
+LoggingService:
+- `ingestion` (Kafka to entity mapping)
+- `history.api` (gRPC/REST adapters)
+- `dashboard.query` (aggregates + cache facade)
+
+---
+
+## 4. Priority Mapping (Flaw -> SOLID-Driven Fix)
+
+1. Secret leaks and hardcoded config -> DIP (externalized secure config) + SRP (separate secret loading responsibility).
+2. `permitAll` and CSRF disable -> DIP (policy abstraction) + SRP (security policy isolated).
+3. UUID-only blacklist and IP handling gaps -> SRP (context extraction), OCP (extensible block keys).
+4. MCP redundant analysis + blocked-user reprocessing -> SRP (pipeline steps), ISP (strategy classes by behavior), LSP (sync/async split).
+5. Missing gRPC server + client mismatch -> DIP (ports/adapters), SRP (transport adapter separation).
+6. Dashboard heavy DB load -> SRP (cache facade), OCP (cache strategy extension).
+7. `parallelStream` misuse and `record.toString()` metadata storage -> SRP + DIP (serialization abstraction).
+
+---
+
+## 5. Delivery Order for Implementation
+
+1. Phase 0 + Phase 1 (security-critical).
+2. Phase 2 + Phase 3 (pipeline correctness and stability).
+3. Phase 4 (data/query efficiency and observability quality).
+4. Phase 5 + Phase 6 (resilience baseline and proof documentation).
+
+This order minimizes immediate breach risk first, then removes systemic runtime bottlenecks.
diff --git a/UI/sentinel-gateway-ui/.codex b/UI/sentinel-gateway-ui/.codex
new file mode 100644
index 0000000..e69de29
diff --git a/UI/sentinel-gateway-ui/src/features/dashboard/Dashboard.jsx b/UI/sentinel-gateway-ui/src/features/dashboard/Dashboard.jsx
index fd877e0..7546734 100644
--- a/UI/sentinel-gateway-ui/src/features/dashboard/Dashboard.jsx
+++ b/UI/sentinel-gateway-ui/src/features/dashboard/Dashboard.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
import { Activity, ShieldCheck, ShieldAlert, Zap, ArrowUpRight, ArrowDownRight, Terminal, Box, Database, Cpu } from 'lucide-react';
import { motion } from 'framer-motion';
import { useQuery } from '@tanstack/react-query';
@@ -55,19 +55,21 @@ const StatCard = ({ title, value, change, icon: Icon, trend, delay = 0 }) => (
);
const Dashboard = () => {
+ const [viewMode, setViewMode] = useState('live');
+
+ const windowMinutes = useMemo(() => (viewMode === 'live' ? 60 : 360), [viewMode]);
+
// React Query for polling dashboard stats
const { data: stats, isLoading: loading, isError: error, refetch } = useQuery({
- queryKey: ['dashboardStats'],
+ queryKey: ['dashboardStats', windowMinutes],
queryFn: async () => {
const end = new Date();
- const start = new Date(Date.now() - 3600000); // Last hour
+ const start = new Date(Date.now() - windowMinutes * 60000);
return await logApi.getDashboardSummary(start.toISOString(), end.toISOString());
},
- refetchInterval: 5000, // Poll every 5s
+ refetchInterval: viewMode === 'live' ? 5000 : false,
});
- const [chartData, setChartData] = useState([]);
-
const formatValue = (val) => {
if (!val && val !== 0) return '0';
if (val >= 1000000) return (val / 1000000).toFixed(1) + 'M';
@@ -75,7 +77,7 @@ const Dashboard = () => {
return val;
};
- if (error && loading) {
+ if (error) {
return (
@@ -101,7 +103,7 @@ const Dashboard = () => {
NETWORK OS
-
+
Sentinel analytics engine processing multi-vector traffic with neural precision.
@@ -109,11 +111,17 @@ const Dashboard = () => {
-
@@ -167,7 +175,7 @@ const Dashboard = () => {
Global request distribution in 1m windows
-
+
Core Flow
@@ -176,11 +184,14 @@ const Dashboard = () => {
Threat Vectors
+
+ {viewMode === 'live' ? 'Auto-refresh every 5s' : 'Snapshot mode'}
+
- {loading && chartData?.length === 0 && (
+ {loading && (
@@ -188,7 +199,10 @@ const Dashboard = () => {
)}
-
+
diff --git a/UI/sentinel-gateway-ui/src/features/dashboard/StatsChart.jsx b/UI/sentinel-gateway-ui/src/features/dashboard/StatsChart.jsx
index e5c9a18..d84f88d 100644
--- a/UI/sentinel-gateway-ui/src/features/dashboard/StatsChart.jsx
+++ b/UI/sentinel-gateway-ui/src/features/dashboard/StatsChart.jsx
@@ -2,12 +2,12 @@ import React, { useEffect, useState } from 'react';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { logApi } from '../../shared/api/client';
-const StatsChart = () => {
+const StatsChart = ({ windowMinutes = 30, refreshInterval = 5000 }) => {
const [chartData, setChartData] = useState([]);
const fetchVelocity = () => {
const end = new Date();
- const start = new Date(Date.now() - 30 * 60000); // Last 30 mins
+ const start = new Date(Date.now() - windowMinutes * 60000);
logApi.getTrafficVelocity(start.toISOString(), end.toISOString())
.then(res => {
@@ -26,9 +26,10 @@ const StatsChart = () => {
useEffect(() => {
fetchVelocity();
- const interval = setInterval(fetchVelocity, 5000);
+ if (!refreshInterval) return undefined;
+ const interval = setInterval(fetchVelocity, refreshInterval);
return () => clearInterval(interval);
- }, []);
+ }, [windowMinutes, refreshInterval]);
return (
diff --git a/UI/sentinel-gateway-ui/src/features/logs/LogsView.jsx b/UI/sentinel-gateway-ui/src/features/logs/LogsView.jsx
index d37c90b..7ae79a1 100644
--- a/UI/sentinel-gateway-ui/src/features/logs/LogsView.jsx
+++ b/UI/sentinel-gateway-ui/src/features/logs/LogsView.jsx
@@ -4,6 +4,7 @@ import { motion, AnimatePresence } from 'framer-motion';
import { useQuery } from '@tanstack/react-query';
import { logApi } from '../../shared/api/client';
import { useReactTable, getCoreRowModel, getPaginationRowModel, getSortedRowModel, flexRender, createColumnHelper } from '@tanstack/react-table';
+import { useSearchParams } from 'react-router-dom';
const StatusBadge = ({ code }) => {
const isError = code >= 400;
@@ -109,14 +110,22 @@ const columns = [
];
const LogsView = () => {
+ const [searchParams] = useSearchParams();
const [autoRefresh, setAutoRefresh] = useState(true);
- const [filterPath, setFilterPath] = useState('');
+ const [filterPath, setFilterPath] = useState(searchParams.get('path') || '');
const [filterStatus, setFilterStatus] = useState('');
+ const [selectedLog, setSelectedLog] = useState(null);
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 50,
});
+ React.useEffect(() => {
+ const queryPath = searchParams.get('path') || '';
+ setFilterPath(queryPath);
+ setPagination((prev) => ({ ...prev, pageIndex: 0 }));
+ }, [searchParams]);
+
const { data: logsResponse, isLoading: loading, isFetching } = useQuery({
queryKey: ['logs', pagination.pageIndex, pagination.pageSize, filterPath, filterStatus],
queryFn: async () => {
@@ -195,6 +204,17 @@ const LogsView = () => {
{autoRefresh ? 'Live' : 'Stopped'}
+ {
+ setFilterPath('');
+ setFilterStatus('');
+ setSelectedLog(null);
+ setPagination((prev) => ({ ...prev, pageIndex: 0 }));
+ }}
+ className="px-4 py-2.5 rounded-2xl border border-slate-300 dark:border-white/10 text-slate-500 dark:text-slate-400 text-xs font-black uppercase tracking-widest hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-white/10 transition-all"
+ >
+ Reset
+
@@ -299,8 +319,9 @@ const LogsView = () => {
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.2 }}
key={row.id}
- className="hover:bg-white/[0.02] transition-all group"
- >
+ className="hover:bg-white/[0.02] transition-all group cursor-pointer"
+ onClick={() => setSelectedLog(row.original)}
+ >
{row.getVisibleCells().map(cell => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
@@ -337,6 +358,33 @@ const LogsView = () => {
+
+
+ {selectedLog && (
+
+
+
+ Selected Event
+ {selectedLog.path || 'Unknown path'}
+
+ setSelectedLog(null)}
+ className="px-4 py-2 rounded-xl border border-slate-300 dark:border-white/10 text-[10px] font-black uppercase tracking-widest text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-white/10 transition-all self-start"
+ >
+ Dismiss
+
+
+
+ {JSON.stringify(selectedLog, null, 2)}
+
+
+ )}
+
);
};
diff --git a/UI/sentinel-gateway-ui/src/shared/components/Layout.jsx b/UI/sentinel-gateway-ui/src/shared/components/Layout.jsx
index db66b6d..f866164 100644
--- a/UI/sentinel-gateway-ui/src/shared/components/Layout.jsx
+++ b/UI/sentinel-gateway-ui/src/shared/components/Layout.jsx
@@ -1,6 +1,25 @@
-import React, { useState, useEffect } from 'react';
-import { NavLink, Outlet, useLocation } from 'react-router-dom';
-import { LayoutDashboard, ListFilter, ShieldAlert, Activity, Settings, Bell, Search, User, Sun, Moon, Server, Network, Zap, Layers, Database } from 'lucide-react';
+import React, { useState, useEffect, useMemo, useRef } from 'react';
+import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom';
+import {
+ LayoutDashboard,
+ ListFilter,
+ ShieldAlert,
+ Activity,
+ Bell,
+ Search,
+ User,
+ Sun,
+ Moon,
+ Server,
+ Network,
+ Zap,
+ Layers,
+ Database,
+ Menu,
+ X,
+ Command,
+ ArrowRight,
+} from 'lucide-react';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { motion, AnimatePresence } from 'framer-motion';
@@ -9,10 +28,26 @@ function cn(...inputs) {
return twMerge(clsx(inputs));
}
+const navItems = [
+ { name: 'Dashboard', path: '/', icon: LayoutDashboard },
+ { name: 'Service Registry', path: '/registry', icon: Network, isSubItem: true },
+ { name: 'Infrastructure Node', path: '/infrastructure', icon: Server },
+ { name: 'Kafka Cluster', path: '/infrastructure/kafka', icon: Zap, isSubItem: true },
+ { name: 'Redis Cache', path: '/infrastructure/redis', icon: Layers, isSubItem: true },
+ { name: 'PostgreSQL', path: '/infrastructure/postgres', icon: Database, isSubItem: true },
+ { name: 'Admin Logs', path: '/logs', icon: ListFilter },
+ { name: 'Blacklist', path: '/blacklist', icon: ShieldAlert },
+ { name: 'Request Flow', path: '/flow', icon: Activity },
+];
+
const Layout = () => {
const location = useLocation();
+ const navigate = useNavigate();
+ const searchInputRef = useRef(null);
+
+ const [sidebarOpen, setSidebarOpen] = useState(false);
+ const [commandQuery, setCommandQuery] = useState('');
- // Theme Management
const [theme, setTheme] = useState(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('theme');
@@ -31,42 +66,114 @@ const Layout = () => {
localStorage.setItem('theme', theme);
}, [theme]);
+ useEffect(() => {
+ setSidebarOpen(false);
+ setCommandQuery('');
+ }, [location.pathname]);
+
+ useEffect(() => {
+ const onKeyDown = (event) => {
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
+ event.preventDefault();
+ searchInputRef.current?.focus();
+ }
+ };
+
+ window.addEventListener('keydown', onKeyDown);
+ return () => window.removeEventListener('keydown', onKeyDown);
+ }, []);
+
const toggleTheme = () => setTheme(theme === 'dark' ? 'light' : 'dark');
- const navItems = [
- { name: 'Dashboard', path: '/', icon: LayoutDashboard },
- { name: 'Service Registry', path: '/registry', icon: Network, isSubItem: true },
- { name: 'Infrastructure Node', path: '/infrastructure', icon: Server },
- { name: 'Kafka Cluster', path: '/infrastructure/kafka', icon: Zap, isSubItem: true },
- { name: 'Redis Cache', path: '/infrastructure/redis', icon: Layers, isSubItem: true },
- { name: 'PostgreSQL', path: '/infrastructure/postgres', icon: Database, isSubItem: true },
- { name: 'Admin Logs', path: '/logs', icon: ListFilter },
- { name: 'Blacklist', path: '/blacklist', icon: ShieldAlert },
- { name: 'Request Flow', path: '/flow', icon: Activity },
- ];
+ const matchedItems = useMemo(() => {
+ const query = commandQuery.trim().toLowerCase();
+ if (!query) return [];
+ return navItems.filter((item) => item.name.toLowerCase().includes(query) || item.path.toLowerCase().includes(query));
+ }, [commandQuery]);
+
+ const currentPageLabel = useMemo(() => {
+ const exactMatch = navItems.find((item) => item.path === location.pathname);
+ if (exactMatch) return exactMatch.name;
+
+ const nestedMatch = navItems.find(
+ (item) => item.path !== '/' && location.pathname.startsWith(item.path),
+ );
+
+ return nestedMatch?.name || 'Overview';
+ }, [location.pathname]);
+
+ const onSearchSubmit = (event) => {
+ event.preventDefault();
+
+ if (!commandQuery.trim()) {
+ searchInputRef.current?.focus();
+ return;
+ }
+
+ const exactMatch = navItems.find(
+ (item) =>
+ item.name.toLowerCase() === commandQuery.trim().toLowerCase() ||
+ item.path.toLowerCase() === commandQuery.trim().toLowerCase(),
+ );
+
+ const route = exactMatch?.path || matchedItems[0]?.path;
+ if (route) {
+ navigate(route);
+ }
+ };
return (
- {/* Background Orbs */}
-
+
+
+
+ {sidebarOpen && (
+ setSidebarOpen(false)}
+ className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm lg:hidden"
+ />
+ )}
+
- {/* Sidebar */}
- |