feat: ja4-platform monorepo — 5 services unified, tests & RPM builds standardized
Services: - ja4sentinel: TLS/JA4 fingerprint capture daemon (Go, libpcap) - logcorrelator: JA4 log correlation engine (Go, ClickHouse) - mod_reqin_log: Apache module (C, JSON request logging) - bot_detector: ML bot detection pipeline (Python) - dashboard: FastAPI/Streamlit analytics UI (Python) Shared libraries: - shared/go/ja4common: logger, config, shutdown, ipfilter (Go module) - shared/python/ja4_common: ClickHouseClient, ClickHouseSettings (Python package) - shared/clickhouse/: canonical SQL migrations (10 files) Build & packaging: - Unified 3-stage Dockerfile.package for Go RPMs (el8/el9/el10) - go.work workspace linking sentinel, correlator, ja4common - Makefile with test-all, build-all, rpm-* targets Fixes applied: - go.work: 1.21 → 1.24.6 (required by sentinel) - correlator Dockerfiles: golang:1.21 → golang:1.24 - replace directives in go.mod for ja4common local path - pyproject.toml: setuptools.backends → setuptools.build_meta - Removed static libpcap linking (unavailable on Rocky 9) - Fixed data races in output/writers_test.go (sync.Mutex + atomic.Int32) - Rewrote corrupted test files (logger_test.go × 2) Test coverage: - correlator: 67.1% total (unixsocket 80.5%, config 91.7%, app 83.3%, multi 87.7%, stdout 100%) - sentinel: all 10 packages pass (api, capture, config, fingerprint, ipfilter, logging, output, tlsparse) Documentation: - README.md + docs/ (architecture, development, 5 services, shared libs, DB schema & migrations) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
74
services/dashboard/frontend/src/ThemeContext.tsx
Normal file
74
services/dashboard/frontend/src/ThemeContext.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { CONFIG } from './config';
|
||||
|
||||
export type Theme = 'dark' | 'light' | 'auto';
|
||||
|
||||
interface ThemeContextValue {
|
||||
theme: Theme;
|
||||
resolved: 'dark' | 'light';
|
||||
setTheme: (t: Theme) => void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextValue>({
|
||||
theme: CONFIG.DEFAULT_THEME,
|
||||
resolved: 'dark',
|
||||
setTheme: () => {},
|
||||
});
|
||||
|
||||
const STORAGE_KEY = CONFIG.THEME_STORAGE_KEY;
|
||||
|
||||
function resolveTheme(theme: Theme): 'dark' | 'light' {
|
||||
if (theme === 'auto') {
|
||||
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
||||
}
|
||||
return theme;
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, setThemeState] = useState<Theme>(() => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null;
|
||||
return stored ?? CONFIG.DEFAULT_THEME;
|
||||
});
|
||||
|
||||
const [resolved, setResolved] = useState<'dark' | 'light'>(() => resolveTheme(
|
||||
(localStorage.getItem(STORAGE_KEY) as Theme | null) ?? CONFIG.DEFAULT_THEME
|
||||
));
|
||||
|
||||
const applyTheme = (t: Theme) => {
|
||||
const r = resolveTheme(t);
|
||||
setResolved(r);
|
||||
document.documentElement.setAttribute('data-theme', r);
|
||||
};
|
||||
|
||||
const setTheme = (t: Theme) => {
|
||||
setThemeState(t);
|
||||
localStorage.setItem(STORAGE_KEY, t);
|
||||
applyTheme(t);
|
||||
};
|
||||
|
||||
// Apply on mount
|
||||
useEffect(() => {
|
||||
applyTheme(theme);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Watch system preference changes when in 'auto' mode
|
||||
useEffect(() => {
|
||||
if (theme !== 'auto') return;
|
||||
const mq = window.matchMedia('(prefers-color-scheme: light)');
|
||||
const handler = () => applyTheme('auto');
|
||||
mq.addEventListener('change', handler);
|
||||
return () => mq.removeEventListener('change', handler);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, resolved, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
return useContext(ThemeContext);
|
||||
}
|
||||
Reference in New Issue
Block a user