feat: initial public release

ConsentOS — a privacy-first cookie consent management platform.

Self-hosted, source-available alternative to OneTrust, Cookiebot, and
CookieYes. Full standards coverage (IAB TCF v2.2, GPP v1, Google
Consent Mode v2, GPC, Shopify Customer Privacy API), multi-tenant
architecture with role-based access, configuration cascade
(system → org → group → site → region), dark-pattern detection in
the scanner, and a tamper-evident consent record audit trail.

This is the initial public release. Prior development history is
retained internally.

See README.md for the feature list, architecture overview, and
quick-start instructions. Licensed under the Elastic Licence 2.0 —
self-host freely; do not resell as a managed service.
This commit is contained in:
James Cottrill
2026-04-13 14:20:15 +00:00
commit fbf26453f2
341 changed files with 62807 additions and 0 deletions

90
apps/admin-ui/src/App.tsx Normal file
View File

@@ -0,0 +1,90 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import {
BrowserRouter,
Navigate,
Route,
Routes,
useLocation,
} from 'react-router-dom';
import Layout from './components/Layout';
import { trackPageView } from './services/analytics';
import ProtectedRoute from './components/ProtectedRoute';
import ComplianceDashboardPage from './pages/ComplianceDashboardPage';
import LoginPage from './pages/LoginPage';
import SettingsPage from './pages/SettingsPage';
import SiteDetailPage from './pages/SiteDetailPage';
import SiteGroupDetailPage from './pages/SiteGroupDetailPage';
import SitesPage from './pages/SitesPage';
import { useAuthStore } from './stores/auth';
import { discoverExtensions, getPages } from './extensions/registry';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
staleTime: 30_000,
},
},
});
function AppRoutes() {
const { loadUser, isAuthenticated } = useAuthStore();
const location = useLocation();
const [extensionsReady, setExtensionsReady] = useState(false);
useEffect(() => {
loadUser();
discoverExtensions().then(() => setExtensionsReady(true));
}, [loadUser]);
useEffect(() => {
trackPageView(location.pathname);
}, [location.pathname]);
const extensionPages = extensionsReady ? getPages() : [];
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
element={
<ProtectedRoute>
<Layout />
</ProtectedRoute>
}
>
<Route path="/sites" element={<SitesPage />} />
<Route path="/sites/:siteId" element={<SiteDetailPage />} />
<Route path="/groups/:groupId" element={<SiteGroupDetailPage />} />
<Route path="/compliance" element={<ComplianceDashboardPage />} />
<Route path="/settings" element={<SettingsPage />} />
{extensionPages
.filter((p) => p.protected !== false)
.map((p) => (
<Route key={p.path} path={p.path} element={<p.component />} />
))}
</Route>
{extensionPages
.filter((p) => p.protected === false)
.map((p) => (
<Route key={p.path} path={p.path} element={<p.component />} />
))}
<Route
path="*"
element={<Navigate to={isAuthenticated ? '/sites' : '/login'} replace />}
/>
</Routes>
);
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
</QueryClientProvider>
);
}