Skip to content

Feature components

Every user-facing screen in the Lockstep VM frontend is an Astro route that mounts exactly one feature component as a React island (client:load). Feature components live directly in src/components/*.tsx; the shared design-system primitives they compose live in src/components/ui/ (see UI primitives). This page catalogs the feature components. For the route wiring pattern, see Pages & routes; for data/client conventions, see Conventions.

Conventions shared by feature components

Follow these when adding or editing a feature component — they are near-universal in this codebase:

ConventionDetail
Own QueryClientAlmost every feature component creates its own new QueryClient(...) and wraps its content in <QueryClientProvider>. There is no app-wide provider. The exported component is a thin wrapper; the real UI is an inner Content / *Content function. Default options are usually { queries: { refetchOnWindowFocus: false, retry: 1 } }.
Two API access stylesNewer/refactored components import typed helpers from @/lib/api (inventoryApi, devicesApi, authApi, dashboardApi, …) which use the shared axios api instance (baseURL = ${PUBLIC_API_URL}/api/v1, withCredentials: true). Older components call fetch('http://localhost:8000/api/v1/...', { credentials: 'include' }) directly against a hardcoded const API. Both rely on the Better Auth session cookie; no bearer header is sent.
Named + default exportComponents export both a named export (export function Foo) and export default Foo.
Props are minimalList/hub components take no props. Detail components take a single identifier prop (cveId, deviceId, appName, upn, sourceKey, scanId, projectId) passed from the Astro route’s dynamic segment.
NavigationRow/link clicks navigate with window.location.assign(...) or <a href> — full page loads, not client routing.

Layout & chrome

These wrap every authenticated page. See Pages & routes for how they nest: Layout.astro → AuthCheck → AppLayout → <FeaturePage>.

AuthCheck

Client-side route guard. Rendered as the outermost island on every protected page.

AspectDetail
Props{ children: React.ReactNode }
RendersA centered spinner while state === 'pending'; null when denied; children when ok.
BehaviorOn mount, fetch('/api/auth/get-session', { credentials: 'include' }) (the Astro Better Auth endpoint, not the FastAPI backend). If body.user exists → ok; otherwise sets denied and redirects to /login. Uses a plain effect (not authClient.useSession()) to keep SSR and first client render identical and avoid React #418/#423 hydration mismatches.
react-queryNone.
Used byEvery protected page (index, devices, applications, users, all integration pages, etc.).

AppLayout

Thin alias — AppLayout just renders <AppShell>{children}</AppShell>. Kept so existing Astro pages don’t need editing. Props: { children: React.ReactNode }.

AppShell

Root v2 layout: ThemeProvider + Sidebar + TopBar + <main> + footer + <Toaster>.

AspectDetail
Props{ children, currentPath?, requireAuth = true }
BehaviorWhen requireAuth, on mount fetches /api/auth/get-session; redirects to /login if no user. Seeds user from the session (email, namefull_name, role), then best-effort enriches via authApi.getCurrentUser() (GET /auth/me) — a backend hiccup never logs anyone out. Renders children only once ready. currentPath defaults to window.location.pathname.
react-queryNone (raw fetch + authApi).

Static left navigation. Props: { currentPath?: string }. Renders the “Lockstep VM” brand and grouped nav links defined in a local groups array: Overview (Dashboard), Inventory (Assets, Software, Users, Vulnerabilities, Version Tracking), Intelligence (Security News, Advisories, Threat Intel, Weaknesses, Vendors & Products), Sources (SentinelOne, Rapid7, Intune, Entra ID, ConnectWise, CW Automate, Upload Data), Work (Projects, Reports, Statistics, Subscriptions), Administration (Organizations, Tags), plus footer links Profile & Usage/Billing. isActive(current, href) highlights the active item. No data fetching.

TopBar

Sticky header inside the content column. Props: { user?: { email?, full_name? } }. Renders a global-search button (⌘K placeholder, non-functional), notifications bell, ThemeToggle, and a user Menu (Profile → /profile, Settings → /organizations, Sign out). logout() calls authClient.signOut(), clears access_token, redirects to /login. Detects Mac to show ⌘K vs Ctrl K.

ThemeProvider

Exports ThemeProvider (applies .v2 + dark/light class to <html>, persists choice) and ThemeToggle (button used by TopBar). Wraps the whole app via AppShell.

Legacy top nav (not used by the v2 AppShell chrome). Renders brand + links and a sign-out that calls authClient.signOut(). Retained but effectively superseded by Sidebar/TopBar.


Inventory

The core asset/software/vulnerability browsing surfaces.

DevicesTable

The Assets list (/devices). A structured, server-paginated, query-buildable device table joined across Intune, SentinelOne, and Rapid7.

AspectDetail
PropsNone.
RendersPageHeader (Export / Integrate menus + Scan stub), a 5-tile KPI strip (Total / Intune / SentinelOne / Rapid7 / Matched), a search bar + Query builder drawer trigger, results toolbar (range, page size), a DataTable, and pagination. Rows link to /device/{device_id}. SourceBadges renders per-source chips (intune/sentinelone/rapid7/connectwise_automate/autoelevate) with a Link2 “matched across sources” marker.
StateURL-synced search, ps (pageSize), p (page); sort; query-builder Group state (query, qbDraft, qbOpen) built from the DEVICE_FIELDS schema (General / Microsoft Intune intune.* / SentinelOne s1.* / Rapid7 r7.*). copyQueryUrl() serializes state (incl. base64 query) to the clipboard.
APIdevicesApi.searchDevices({ query, search, skip, limit, sort })POST /devices/search; devicesApi.getStats()GET /devices/stats.
react-query['devices-search', search, toServerPayload(query), skip, pageSize, sort], ['device-stats']

ApplicationsTable

The Software inventory list (/applications). Same architecture as DevicesTable but for software groups.

AspectDetail
PropsNone.
RendersPageHeader (Export / Import → /inventory / Integrate / Scan stub), search bar + Query builder drawer, results toolbar, DataTable (Name → /application/{name}, Asset Count, Vendor, Product, Version with +N roll-up, Site), pagination.
StateURL-synced search/ps/p, sort, query builder over APP_FIELDS (General + Microsoft Intune metadata: publisher, platform, size_bytes, category, install_status). Field ids must match the backend APPLICATION_FIELDS allowlist.
APIinventoryApi.searchApplications({ query, search, skip, limit, sort })POST /inventory/applications/search.
react-query['applications-search', search, toServerPayload(query), skip, pageSize, sort]

ApplicationDetail

Software detail page (/application/[appName]). Props: { appName: string }.

AspectDetail
RendersHeader with “Discovered via” source chips (SOURCE_LABELS), 4 summary cards (Total Versions, Devices, Most Common version, Vulnerabilities), Recent Vulnerability Scans (link to /vulnerabilities/scans/{scan_id}), Installed-on-Devices table, a Version Distribution table with per-version checkboxes, and Platforms.
StateselectedVersions (checkbox multi-select), isScanning. The “Scan selected versions” button POSTs /vulnerabilities/scan with { application_name, vendor, versions, enrich: true }. Uses staleTime: 0, gcTime: 0 (always fresh). Uses raw fetch against hardcoded localhost:8000.
APIGET /inventory/applications?search=^{name}$&limit=100 (exact match), GET /vulnerabilities/scans?application_name={name}, GET /devices/by-application/{name}?limit=100, POST /vulnerabilities/scan.
react-query['application-detail', appName], ['application-vulnerabilities', appName], ['application-devices', appName]

AssetDetail

The primary device detail page (/device/[deviceName], prop is actually the device id). Props: { deviceId: string }. Large multi-source aggregation view.

AspectDetail
RendersHeader with SourceChips, KPI cards, source Tabs/SourceTabs (Inventory, SentinelOne, Rapid7, ConnectWise Automate, AutoElevate) with AttributeTable/KeyValueGrid per source, a vulnerabilities/findings table, export-JSON action, and per-CVE research action. NotInSource/EmptyNoIssues handle empty states. Flattens nested backend objects into dot-path attribute rows via flattenAttributes.
StateactiveSource tab, researchingCve, researchedCves (Set), loadedAt.
MutationsscanMutationPOST /vulnerabilities/scan-device ({ device_name, applications }), invalidates ['asset-vulns']. researchFinding(vulnId)POST /rapid7/research-finding.
APIdevicesApi.getDeviceById(deviceId); GET /inventory/items?device_name=; GET /vulnerabilities/?device_name=; GET /sentinelone/device-by-name/{name}; GET /rapid7/device-by-name/{name}; GET /connectwise-automate/device-by-name/{name}; GET /csv-sources/autoelevate/device-by-name/{name}; GET /rapid7/findings/{assetId}?limit=500.
react-query['asset', deviceId], ['asset-apps', name], ['asset-vulns', name], ['asset-s1', name], ['asset-r7', name], ['asset-cwa', name], ['asset-ae', name], ['asset-r7-findings', rapid7AssetId]

DeviceDetail

Older/parallel device detail component (prop deviceId). Same shape as AssetDetail (multi-source device view with apps, vulns, Rapid7 findings, SentinelOne data, scan-device + research-finding actions) using raw fetch. Present in the tree; AssetDetail is the one wired to the /device/[deviceName] route.

AspectDetail
Props{ deviceId: string }
APIGET /inventory/items?device_name=, GET /vulnerabilities/?device_name=, GET /rapid7/findings/{assetId}, POST /rapid7/research-finding, POST /vulnerabilities/scan-device.
react-query['device-detail', deviceId], ['device-apps', device?.device_name], ['device-vulns', device?.device_name], ['device-rapid7', device?.device_name], ['device-rapid7-findings', rapid7AssetId], ['device-sentinel', device?.device_name]

VulnerabilitiesTable

The Vulnerabilities list (/vulnerabilities). Props: none.

AspectDetail
RendersVulnerability stats, a scans list, and a searchable CVE table.
StatesearchTerm.
APIGET /vulnerabilities/, GET /vulnerabilities/scans, GET /vulnerabilities/stats.
react-query['vulnerability-scans'], ['vulnerabilities', searchTerm], ['vulnerability-stats']

VulnerabilityDetail

CVE detail page (/vulnerabilities/[cve] and /vulnerabilities/cve/[cveId]). Props: { cveId: string }.

AspectDetail
RendersCVE metadata, affected-devices list, CVE tags, and subscription-status checks for the affected vendor/product.
APIvulnerability detail, affected devices, CVE tags, all-tags, subscription checks by product & vendor.
react-query['vulnerability', cveId], ['cve-tags', cveId], ['all-tags'], ['sub-check-product', vulnData?.application_name], ['sub-check-vendor', vulnData?.vendor], ['cve-devices', cveId]

ScanDetail

Single scan result page (/vulnerabilities/scans/[scanId]). Props: { scanId: string }. Fetches GET /vulnerabilities/scans/{scanId} and its vulnerabilities.

react-query['scan', scanId], ['scan-vulnerabilities', scanId]

WeaknessesPage

CWE weaknesses browser (/weaknesses). Props: none. Search + category filter over a weaknesses list.

react-query['weaknesses', search, categoryFilter]

VendorsProducts

Vendors & Products explorer (/vendors). Props: none. Combines local inventory vendors, subscriptions, projects, and live NVD lookups (CVE keyword search + CPE search).

react-query['inventory-vendors'], ['subscriptions'], ['projects'], ['nvd-cves', nvdKeyword], ['nvd-cpe', cpeSubmitted, cpeStartIndex]

Users

UsersPage

The Users list (/users). Props: none. People synced from Microsoft Entra ID, linked to owned assets.

AspectDetail
RendersHeader, search bar with “Enabled only” toggle, a paginated table (Name/UPN, Department, Title, Groups count, Status). Row click → /user/{upn}. Includes a UserDrawer slide-over (used for inline detail) that fetches a single user. Page size 25.
Statesearch/q (submitted), enabledOnly, page, selected.
APIGET /entra/users?skip&limit&search&enabled_only; drawer: GET /entra/user/{upn}. Raw fetch, const E = ${API}/entra.
react-query['users', q, enabledOnly, page], ['user', upn] (drawer)

UserDetail

User detail page (/user/[upn]). Props: { upn: string }.

AspectDetail
RendersHeader (avatar, door-admin/enabled badges), KPI cards (Assets, Groups, Status, Department), a source-tabbed AttributeTable (entra.id + enrichment sources like badge_access/hr_roster), a Profile card, an Assets list (links to /device/{device_id}), and filterable Group memberships. Reuses the same SourceTabs/AttributeTable UI as AssetDetail.
StategroupSearch, activeSource.
APIGET /entra/user/{upn}.
react-query['user-detail', upn]

Integrations

Source-connector admin pages. Each shows connection config, a sync trigger, stats, and browsable synced data. They follow a common pattern: config query, stats query, sync-status query (polled while running), plus test-connection / save / sync / delete-config mutations.

SentinelOnePage

/sentinelone. Props: none. Tabs: overview / apps / devices / threats (plus a config setup view).

AspectDetail
RendersConnection/config panel, stats KPIs, sync controls, and tabbed tables for applications, devices, and threats (each with its own search box). Uses a local apiFetch(path, opts) helper over const API.
MutationsPOST /sentinelone/test-connection, POST /sentinelone/config (save), POST /sentinelone/sync, DELETE /sentinelone/config.
API (queries)GET /sentinelone/config, /stats, /sync-status, /apps, /devices, /threats.
react-query['s1-config'], ['s1-stats'], ['s1-sync-status'] (polls while syncing), ['s1-apps', searchApps, minDevices], ['s1-devices', searchDevices], ['s1-threats', searchThreats]

Rapid7Page

/rapid7. Props: none. Tabs: overview / apps / assets (plus config).

AspectDetail
RendersSame shape as SentinelOnePage — config, stats, sync, and tabbed apps/assets tables via apiFetch.
MutationsPOST /rapid7/test-connection, POST /rapid7/config, POST /rapid7/sync, DELETE /rapid7/config.
API (queries)GET /rapid7/config, /stats, /sync-status, /apps, /assets.
react-query['r7-config'], ['r7-stats'], ['r7-sync-status'], ['r7-apps', searchApps, minDevices], ['r7-assets', searchAssets]

IntunePage

/intune. Props: none. Includes an inline SetupWizard, plus DevicesTab and AppsTab sub-components.

AspectDetail
RendersIf unconfigured, a multi-step SetupWizard (POST /intune/config, POST /intune/test-connection). Once configured: stats KPIs, sync control, a Devices tab (GET /intune/devices?limit=200) and an Apps tab (GET /intune/apps with search + minDevices filter).
Mutationssave config, test-connection, POST /intune/sync, DELETE /intune/config.
API (queries)GET /intune/config, /stats, /devices, /apps.
react-query['intune-config'], ['intune-stats'], ['intune-devices'], ['intune-apps', search, minDevices]

EntraPage

/entra — Microsoft Entra ID (Azure AD) connector. Props: none. Reuses the Intune app registration.

AspectDetail
RendersConnected badge, stats (Users / Groups / Last sync), App-registration card (tenant/client id, cert vs secret), and a Sync panel with “Sync now” + “Test connection” and a link to /users. If not configured, an instructional card.
Mutations (react-query)testMutationPOST /entra/test-connection; syncMutationPOST /entra/sync (invalidates ['entra-status']).
API (queries)GET /entra/config, GET /entra/sync-status (enabled only when configured; polls every 3 s while sync_status === 'running').
react-query['entra-config'], ['entra-status']

ConnectWiseAutomatePage

/connectwise-automate — CW Automate (RMM) connector. Props: none. Inner Content, with a Field input helper.

AspectDetail
RendersConfig form, connection test, sync control, and a clients list. const CWA = ${API}/connectwise-automate.
MutationssaveScopeMutation, saveMutation (POST /connectwise-automate/config), testMutation (POST /connectwise-automate/test-connection), syncMutation (POST /connectwise-automate/sync), deleteMutation (DELETE /connectwise-automate/config).
API (queries)GET /connectwise-automate/config, /clients, /sync-status.
react-query['cwa-config'], ['cwa-clients'], ['cwa-status']

ConnectWisePage

/connectwise — the largest feature component (~2,100 lines): the ConnectWise PSA ticketing workspace. Props: none. Many sub-components.

AspectDetail
RendersConnection status, board list, a recent-tickets table, and a rich TicketDetail modal with tabs overview / notes / assets / time / context. Additional modals: BulkPullModal (bulk-import tickets by board), VulnTicketModal (create a ticket from a vuln, with CVE/app/company search), AdvisoryComposeModal. QuickActions patches status/priority.
Sub-componentsConnectWiseContent, BulkPullModal, VulnTicketModal, TicketDetail, QuickActions, TicketOverviewTab, TicketNotesTab, TicketAssetsTab, TicketTimeTab, TicketContextTab, AdvisoryComposeModal.
MutationsautoConfigMutation (POST /connectwise/auto-config), syncMutation (POST /connectwise/sync), bulkPullMutation (POST /connectwise/bulk-pull?board=), createMutation (POST /connectwise/tickets), createVulnMutation (POST /connectwise/tickets/from-vuln), patchMutation (PATCH /connectwise/service/tickets/{id}), assignMutation, addNoteMutation (POST .../notes), config link/unlink mutations.
Key queriesstatus, boards, service tickets, local tickets, bulk-pull status, projects, ticket notes/configs/time-entries/context, plus modal searches (cve-search-modal, app-search-modal, cw-company-search, cw-config-search).
react-query['cw-status'], ['cw-boards'], ['cw-tickets', filterBoard], ['cw-local-tickets'], ['cw-bulk-status'], ['projects'], ['cw-notes', ticketId], ['cw-ticket-configs', ticketId], ['cw-time-entries', ticketId], ['cw-ticket-context', ticketId], ['cve-search-modal', cveQ], ['app-search-modal', appQ], ['cw-company-search', companyQ], ['cw-config-search', configSearchDebounced, companyId]

CWBulkTicketsPage

/connectwise-bulk — bulk ticket operations. Props: none. Filterable/paginated ticket list with per-ticket notes.

APIGET /connectwise/bulk-pull tickets with q, status/company filters, pagination; per-row notes.
react-query['cw-bulk-tickets', q, statusFilter, companyFilter, page], ['cw-bulk-notes', ticket.ticket_id]

ThreatIntelPage

/threat-intel. Props: none. EPSS / exploit-in-the-wild threat intelligence.

AspectDetail
RendersConfig panel and a high-risk CVE list (EPSS top-50), with per-CVE enrichment.
APIGET /threat-intel/config, GET /threat-intel/epss/high-risk?limit=50, GET /threat-intel/enrich/{cveId}.
react-query['threat-intel-high-risk']

Data sources (CSV / no-API)

CsvSourcesHub

The Data Sources / Upload Data hub (/inventory). Props: none.

AspectDetail
RendersA card grid of CSV source connectors (each: icon, category pill software/identity/access, record count, StatusPill of last import status). Cards link to /csv-source/{key}.
APIGET /csv-sources/. Raw fetch, const API.
react-query['csv-sources']

CsvSourceDetail

Per-source upload page (/csv-source/[key]). Props: { sourceKey: string }.

AspectDetail
RendersHeader + category, stats (record count / last upload / total uploads), a react-dropzone upload area (accepts .csv/.xls/.xlsx), expected-columns hint, and an upload-history table. Clicking a history row opens UploadModal showing parse metrics (row/imported/in-db counts, detected shape, warnings, per-field fill-rate) and a raw sample table. A “Purge data” button DELETEs all imported data.
Stateuploading, msg, openUpload (upload id for modal).
APIGET /csv-sources/{key} (polls every 2.5 s while any upload is processing); POST /csv-sources/{key}/upload (multipart); DELETE /csv-sources/{key}/data; modal: GET /csv-sources/{key}/uploads/{uploadId}.
react-query['csv-source', sourceKey], ['csv-upload', sourceKey, uploadId]

FileUpload

Reusable CSV drop-zone card (not a page). Props: { onUploadComplete?: () => void }. Uses react-dropzone (single .csv), calls inventoryApi.uploadFile(file), shows success (with upload_id) / error state. No react-query. Standalone helper used where a simple upload widget is needed.


Dashboard

DashboardPage

The home dashboard (/, wired via src/components/dashboard/DashboardPage.tsx). Props: none. A configurable widget-grid dashboard, not the legacy Dashboard.tsx.

AspectDetail
RendersA dashboard selector, an edit-mode toggle, and a DashboardCanvas of widgets. In edit mode: add widgets (WidgetPicker/WidgetEditor), drag layout, delete. System dashboards are read-only. Empty state prompts adding the first widget. Widgets render via the @/widgets registry / WidgetRenderer / WidgetShell; data via useWidgetData.
StateselectedId, editMode, draftLayout, editing (widget being edited).
APIdashboardApi (list, default, get by id, addWidget, update/delete).
react-query['dashboards-list'], ['dashboard-default'], ['dashboard', selectedId], ['widget-data']
Supporting filesdashboard/DashboardCanvas.tsx, WidgetShell.tsx, WidgetRenderer.tsx, WidgetPicker.tsx, WidgetEditor.tsx, useWidgetData.ts, fieldCatalogs.ts.

Dashboard (legacy)

Dashboard.tsx — an earlier fixed-layout dashboard (inventory stats, vulnerability stats, subscription activity, recent scans, subscriptions). Props: none. Superseded by dashboard/DashboardPage on the / route but still in the tree.

APIGET /subscriptions/activity?limit=15, GET /vulnerabilities/?limit=15, GET /vulnerabilities/scans, GET /subscriptions/.
react-query['inventory-stats'], ['vulnerability-stats'], ['dashboard-sub-activity'], ['dashboard-scans'], ['dashboard-subs']

DashboardDemo

Static demo/sample dashboard component (no live data). Not mounted on a production route.


Work (projects, reports, statistics, subscriptions)

ProjectsPage

/projects. Props: none. Lists projects with create/edit/delete.

APIGET/POST/PUT/DELETE /projects/.
react-query['projects']

ProjectDetail

/project/[id]. Props: { projectId: string }. Project workspace: vendors, apps, tracked vulnerabilities, and device assignment.

AspectDetail
RendersProject header, vendor/app pickers (searchable), a project-vulnerabilities list, and a device selector. Mutations refetch ['project', projectId] and ['project-vulns', projectId].
react-query['project', projectId], ['project-vulns', projectId], ['vendors-list', vendorSearch], ['apps-list', appSearch], ['devices-all']

StatisticsPage

/statistics. Props: none. Aggregate charts/tiles.

APIGET /vulnerabilities/stats, GET /devices/stats, GET /vulnerabilities/scans.
react-query['stats-vulns'], ['stats-devices'], ['stats-scans']

SubscriptionsPage

/subscriptions. Props: none. Vendor/product notification subscriptions + activity feed, with tag-based subscribe.

react-query['subscriptions'], ['subscription-activity'], ['tags'], ['vendor-tags'], ['product-tags'], ['projects']

ReportsPage

/reports. Props: none. Generate / list / manage reports.

APIGET/POST/DELETE /reports.
react-query['reports']

Intelligence

NewsFeedPage

/news — Security News aggregator. Props: none. The most heavily-filtered list in the app.

AspectDetail
RendersNews-stats header, a feed sidebar/manager, and a filterable, infinite/skip-paginated news-items list. Filters: severity, category, feed, search, exploited, Patch Tuesday. Can link items into projects.
react-query['news-feeds'], ['news-stats'], ['news-items', filterSev, filterCat, filterFeed, filterSearch, filterExploited, filterPatchTuesday, skip], ['projects']

AdvisoryEmailPage

/advisories — compose & send security advisory emails. Props: none.

AspectDetail
RendersAdvisory composer with CVE search and app search, affected-user resolution, draft preview, and a sent/advisories history list. Uses a local apiFetch.
APIPOST /advisories/affected-users, POST /advisories/draft, POST /advisories/send, GET /advisories?limit=100, CVE/app search endpoints.
react-query['advisories'], ['advisory', previewId], ['cve-search', q], ['app-search', q]

Administration

OrganizationsPage

/organizations — tenant members & invites. Props: none. Uses raw fetch(${API}/api/v1{path}).

AspectDetail
RendersMembers list and invites list. Actions: change member role (PATCH), create invite (POST), revoke invite (DELETE), remove member (DELETE .../members/{id}). Role select state `useState<‘user’
APIGET /organizations/members, GET /organizations/invites, PATCH member role, POST /organizations/invites, DELETE invite, DELETE /organizations/members/{id}.

TagsPage

/tags. Props: none. CRUD over tags.

APIGET/POST/DELETE /tags.
react-query['tags']

BillingPage

/billing — Usage & Billing. Props: none. Currently static/mock (hardcoded usage object, “Professional Plan”). No API calls or react-query.

ProfilePage

/profile. Props: none. Currently static/mock — local useState for name/email/notification toggles; no backend wiring yet.


Authentication & account

These render outside the app chrome (no AuthCheck/AppLayout wrapper) and use Better Auth via authClient (@/lib/auth-client) rather than the FastAPI backend. See Conventions for the auth model.

ComponentRoutePropsKey calls
Login/loginnoneauthClient.signIn.email(...)
Signup/signupnoneauthClient.signUp.email(...); GET /invites/{token} to resolve an invite, POST /invites/{token}/accept
ForgotPassword/forgot-passwordnoneBetter Auth password-reset request
ResetPassword/reset-passwordnoneBetter Auth password reset
VerifyEmail/verify-emailnonefetch('/api/auth/verify-email?token=...')
TwoFactor/two-factornoneauthClient.twoFactor.verifyTotp / verifyOtp / sendOtp / verifyBackupCode
MfaSettings/settings/mfanoneauthClient.useSession(), authClient.twoFactor.enable / disable / verifyTotp

None of the auth components use react-query; they drive Better Auth directly and redirect on success.


Quick index (component → route → props)

ComponentRouteProps
AuthCheck(all protected)children
AppLayout / AppShell(all protected)children, (currentPath?, requireAuth?)
Sidebar(chrome)currentPath?
TopBar(chrome)user?
DashboardPage/
DevicesTable/devices
AssetDetail/device/[deviceName]deviceId
ApplicationsTable/applications
ApplicationDetail/application/[appName]appName
VulnerabilitiesTable/vulnerabilities
VulnerabilityDetail/vulnerabilities/[cve], /vulnerabilities/cve/[cveId]cveId
ScanDetail/vulnerabilities/scans/[scanId]scanId
WeaknessesPage/weaknesses
VendorsProducts/vendors
UsersPage/users
UserDetail/user/[upn]upn
SentinelOnePage/sentinelone
Rapid7Page/rapid7
IntunePage/intune
EntraPage/entra
ConnectWisePage/connectwise
CWBulkTicketsPage/connectwise-bulk
ConnectWiseAutomatePage/connectwise-automate
ThreatIntelPage/threat-intel
CsvSourcesHub/inventory
CsvSourceDetail/csv-source/[key]sourceKey
NewsFeedPage/news
AdvisoryEmailPage/advisories
ProjectsPage/projects
ProjectDetail/project/[id]projectId
StatisticsPage/statistics
SubscriptionsPage/subscriptions
ReportsPage/reports
OrganizationsPage/organizations
TagsPage/tags
BillingPage/billing— (mock)
ProfilePage/profile— (mock)
Login / Signup / ForgotPassword / ResetPassword / VerifyEmail / TwoFactorauth routes
MfaSettings/settings/mfa
FileUpload(helper)onUploadComplete?