diff --git a/apps/backend/router.ts b/apps/backend/router.ts index b737010..52d1e95 100644 --- a/apps/backend/router.ts +++ b/apps/backend/router.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from 'express'; -import { VersionResolver } from './common/middleware/versionResolver'; -import { UriVersionStrategy } from './common/strategies/uriStrategy'; -import { HeaderVersionStrategy } from './common/strategies/headerStrategy'; +import { VersionResolver } from './src/common/middleware/versionResolver'; +import { UriVersionStrategy } from './src/common/strategies/uriStrategy'; +import { HeaderVersionStrategy } from './src/common/strategies/headerStrategy'; import { resourceRouterV1 } from './v1/routes/resourceRouter'; import { resourceRouterV2 } from './v2/routes/resourceRouter'; diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index a478cd0..a752bb6 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -1,5 +1,5 @@ import express, { Application } from 'express'; -import { apiRouter } from './api/router'; +import { apiRouter } from '../router'; const app: Application = express(); diff --git a/apps/backend/v1/controllers/resourceController.ts b/apps/backend/v1/controllers/resourceController.ts index 360ec18..8936812 100644 --- a/apps/backend/v1/controllers/resourceController.ts +++ b/apps/backend/v1/controllers/resourceController.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { setDeprecationHeaders } from '../../common/utils/deprecation'; +import { setDeprecationHeaders } from '../../src/common/utils/deprecation'; export class ResourceControllerV1 { public static getResources(req: Request, res: Response): void { diff --git a/apps/dashboard/src/app/globals.css b/apps/dashboard/src/app/globals.css index 9c7906c..80f7324 100644 --- a/apps/dashboard/src/app/globals.css +++ b/apps/dashboard/src/app/globals.css @@ -9,7 +9,7 @@ } body { - @apply bg-gray-950 text-gray-100; + @apply bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100; } /* Scrollbar styling */ @@ -17,19 +17,19 @@ @apply w-1.5 h-1.5; } ::-webkit-scrollbar-track { - @apply bg-gray-900; + @apply bg-gray-100 dark:bg-gray-900; } ::-webkit-scrollbar-thumb { - @apply bg-gray-700 rounded-full; + @apply bg-gray-300 dark:bg-gray-700 rounded-full; } ::-webkit-scrollbar-thumb:hover { - @apply bg-gray-600; + @apply bg-gray-400 dark:bg-gray-600; } } @layer components { .sentinel-card { - @apply bg-gray-900 border border-gray-800 rounded-xl p-6; + @apply bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-xl p-6; } .sentinel-badge { @@ -37,18 +37,18 @@ } .sentinel-badge--critical { - @apply bg-red-950 text-red-400 border border-red-800; + @apply bg-red-50 text-red-700 border border-red-200 dark:bg-red-950 dark:text-red-400 dark:border-red-800; } .sentinel-badge--high { - @apply bg-orange-950 text-orange-400 border border-orange-800; + @apply bg-orange-50 text-orange-700 border border-orange-200 dark:bg-orange-950 dark:text-orange-400 dark:border-orange-800; } .sentinel-badge--medium { - @apply bg-yellow-950 text-yellow-400 border border-yellow-800; + @apply bg-yellow-50 text-yellow-700 border border-yellow-200 dark:bg-yellow-950 dark:text-yellow-400 dark:border-yellow-800; } .sentinel-badge--low { - @apply bg-blue-950 text-blue-400 border border-blue-800; + @apply bg-blue-50 text-blue-700 border border-blue-200 dark:bg-blue-950 dark:text-blue-400 dark:border-blue-800; } } diff --git a/apps/dashboard/src/app/layout.tsx b/apps/dashboard/src/app/layout.tsx index c743374..f46a7f8 100644 --- a/apps/dashboard/src/app/layout.tsx +++ b/apps/dashboard/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from 'next'; import './globals.css'; import { Sidebar } from '@/components/layout/Sidebar'; import { TopBar } from '@/components/layout/TopBar'; +import { ThemeProvider } from '@/components/theme/ThemeProvider'; export const metadata: Metadata = { title: { @@ -20,23 +21,25 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - {/* Sidebar */} - + + + + {/* Sidebar */} + - {/* Main content area */} -
- -
- {children} -
-
+ {/* Main content area */} +
+ +
+ {children} +
+
+
); diff --git a/apps/dashboard/src/components/layout/Sidebar.tsx b/apps/dashboard/src/components/layout/Sidebar.tsx index 735b080..734fb8b 100644 --- a/apps/dashboard/src/components/layout/Sidebar.tsx +++ b/apps/dashboard/src/components/layout/Sidebar.tsx @@ -143,11 +143,11 @@ export function Sidebar() { return ( ); diff --git a/apps/dashboard/src/components/layout/TopBar.tsx b/apps/dashboard/src/components/layout/TopBar.tsx index 8d0d89b..af041db 100644 --- a/apps/dashboard/src/components/layout/TopBar.tsx +++ b/apps/dashboard/src/components/layout/TopBar.tsx @@ -1,6 +1,7 @@ 'use client'; import { usePathname } from 'next/navigation'; +import { ThemeToggle } from '@/components/theme/ThemeToggle'; const pageTitles: Record = { '/': 'Overview', @@ -15,12 +16,12 @@ export function TopBar() { const title = pageTitles[pathname] ?? 'Dashboard'; return ( -
+
{/* Breadcrumb / page title */} -
- Sentinel +
+ Sentinel + {/* Theme toggle */} + + {/* Live indicator */}
void; +} + +const ThemeContext = createContext(undefined); + +export function useTheme() { + const ctx = useContext(ThemeContext); + if (!ctx) throw new Error('useTheme must be used within ThemeProvider'); + return ctx; +} + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [theme, setTheme] = useState('dark'); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + const stored = localStorage.getItem('sentinel-theme') as Theme | null; + const initial = stored ?? 'dark'; + setTheme(initial); + document.documentElement.classList.toggle('dark', initial === 'dark'); + document.documentElement.classList.toggle('light', initial === 'light'); + setMounted(true); + }, []); + + const toggleTheme = () => { + setTheme(prev => { + const next = prev === 'dark' ? 'light' : 'dark'; + localStorage.setItem('sentinel-theme', next); + document.documentElement.classList.remove('dark', 'light'); + document.documentElement.classList.add(next); + return next; + }); + }; + + if (!mounted) { + return <>{children}; + } + + return {children}; +} diff --git a/apps/dashboard/src/components/theme/ThemeToggle.tsx b/apps/dashboard/src/components/theme/ThemeToggle.tsx new file mode 100644 index 0000000..7527f7a --- /dev/null +++ b/apps/dashboard/src/components/theme/ThemeToggle.tsx @@ -0,0 +1,49 @@ +'use client'; + +import { useTheme } from './ThemeProvider'; + +export function ThemeToggle() { + const { theme, toggleTheme } = useTheme(); + + return ( + + ); +} diff --git a/apps/dashboard/tailwind.config.ts b/apps/dashboard/tailwind.config.ts index 9329fd2..bbc9adf 100644 --- a/apps/dashboard/tailwind.config.ts +++ b/apps/dashboard/tailwind.config.ts @@ -1,6 +1,7 @@ import type { Config } from 'tailwindcss'; const config: Config = { + darkMode: 'class', content: [ './src/pages/**/*.{js,ts,jsx,tsx,mdx}', './src/components/**/*.{js,ts,jsx,tsx,mdx}', diff --git a/jest.backend.config.js b/jest.backend.config.js index 71fb5f8..37d5841 100644 --- a/jest.backend.config.js +++ b/jest.backend.config.js @@ -4,6 +4,7 @@ module.exports = { testEnvironment: 'node', rootDir: '.', testMatch: ['/apps/backend/**/*.spec.ts', '/src/**/*.spec.ts'], + testPathIgnorePatterns: ['/node_modules/', '/src/api-keys/'], transform: { '^.+\\.ts$': [ 'ts-jest', diff --git a/tsconfig.json b/tsconfig.json index ccec881..6b0b5a1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,7 @@ "apps/backend/**/*", "libs/**/*", "database/**/*", - "src/**/*", + "src/modules/reports/**/*", "tests/**/*", "prisma.config.ts" ],