chore(frontend): fix lint errors and update biome schema
Fix exhaustive deps, non-null assertion, a11y roles, and error boundary type. Migrate biome config to 2.4.4.
This commit is contained in:
parent
cda830a4e5
commit
65e01d055c
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
|
"$schema": "https://biomejs.dev/schemas/2.4.4/schema.json",
|
||||||
"vcs": {
|
"vcs": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"clientKind": "git",
|
"clientKind": "git",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,7 +3,7 @@
|
||||||
// index.ts
|
// index.ts
|
||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
export * from './useThreats'
|
|
||||||
export * from './useStats'
|
|
||||||
export * from './useModels'
|
|
||||||
export * from './useAlerts'
|
export * from './useAlerts'
|
||||||
|
export * from './useModels'
|
||||||
|
export * from './useStats'
|
||||||
|
export * from './useThreats'
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
|
import { type WebSocketAlert, WebSocketAlertSchema } from '@/api/types'
|
||||||
import { ALERTS, WS_ENDPOINTS } from '@/config'
|
import { ALERTS, WS_ENDPOINTS } from '@/config'
|
||||||
import { WebSocketAlertSchema, type WebSocketAlert } from '@/api/types'
|
|
||||||
|
|
||||||
interface AlertState {
|
interface AlertState {
|
||||||
alerts: WebSocketAlert[]
|
alerts: WebSocketAlert[]
|
||||||
|
|
@ -31,11 +31,9 @@ const useAlertStore = create<AlertState>()((set) => ({
|
||||||
setConnected: (connected) =>
|
setConnected: (connected) =>
|
||||||
set({ isConnected: connected, connectionError: null }),
|
set({ isConnected: connected, connectionError: null }),
|
||||||
|
|
||||||
setError: (error) =>
|
setError: (error) => set({ isConnected: false, connectionError: error }),
|
||||||
set({ isConnected: false, connectionError: error }),
|
|
||||||
|
|
||||||
clear: () =>
|
clear: () => set({ alerts: [], isConnected: false, connectionError: null }),
|
||||||
set({ alerts: [], isConnected: false, connectionError: null }),
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function getWsUrl(): string {
|
function getWsUrl(): string {
|
||||||
|
|
@ -82,7 +80,7 @@ export function useAlerts() {
|
||||||
function scheduleReconnect() {
|
function scheduleReconnect() {
|
||||||
const delay = Math.min(
|
const delay = Math.min(
|
||||||
ALERTS.RECONNECT_BASE_MS * 2 ** retryCountRef.current,
|
ALERTS.RECONNECT_BASE_MS * 2 ** retryCountRef.current,
|
||||||
ALERTS.RECONNECT_MAX_MS,
|
ALERTS.RECONNECT_MAX_MS
|
||||||
)
|
)
|
||||||
retryCountRef.current += 1
|
retryCountRef.current += 1
|
||||||
retryTimerRef.current = setTimeout(connect, delay)
|
retryTimerRef.current = setTimeout(connect, delay)
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,16 @@
|
||||||
|
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { QUERY_KEYS, API_ENDPOINTS } from '@/config'
|
|
||||||
import { apiClient, QUERY_STRATEGIES } from '@/core/api'
|
|
||||||
import type { ModelStatus, RetrainResponse } from '@/api/types'
|
import type { ModelStatus, RetrainResponse } from '@/api/types'
|
||||||
|
import { API_ENDPOINTS, QUERY_KEYS } from '@/config'
|
||||||
|
import { apiClient, QUERY_STRATEGIES } from '@/core/api'
|
||||||
|
|
||||||
export function useModelStatus() {
|
export function useModelStatus() {
|
||||||
return useQuery<ModelStatus>({
|
return useQuery<ModelStatus>({
|
||||||
queryKey: QUERY_KEYS.MODELS.STATUS(),
|
queryKey: QUERY_KEYS.MODELS.STATUS(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiClient.get<ModelStatus>(
|
const { data } = await apiClient.get<ModelStatus>(
|
||||||
API_ENDPOINTS.MODELS.STATUS,
|
API_ENDPOINTS.MODELS.STATUS
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|
@ -28,7 +28,7 @@ export function useRetrain() {
|
||||||
return useMutation<RetrainResponse>({
|
return useMutation<RetrainResponse>({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const { data } = await apiClient.post<RetrainResponse>(
|
const { data } = await apiClient.post<RetrainResponse>(
|
||||||
API_ENDPOINTS.MODELS.RETRAIN,
|
API_ENDPOINTS.MODELS.RETRAIN
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,17 @@
|
||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { QUERY_KEYS, API_ENDPOINTS } from '@/config'
|
|
||||||
import { apiClient, QUERY_STRATEGIES } from '@/core/api'
|
|
||||||
import type { StatsResponse } from '@/api/types'
|
import type { StatsResponse } from '@/api/types'
|
||||||
|
import { API_ENDPOINTS, QUERY_KEYS } from '@/config'
|
||||||
|
import { apiClient, QUERY_STRATEGIES } from '@/core/api'
|
||||||
|
|
||||||
export function useStats(range = '24h') {
|
export function useStats(range = '24h') {
|
||||||
return useQuery<StatsResponse>({
|
return useQuery<StatsResponse>({
|
||||||
queryKey: QUERY_KEYS.STATS.BY_RANGE(range),
|
queryKey: QUERY_KEYS.STATS.BY_RANGE(range),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiClient.get<StatsResponse>(
|
const { data } = await apiClient.get<StatsResponse>(API_ENDPOINTS.STATS, {
|
||||||
API_ENDPOINTS.STATS,
|
params: { range },
|
||||||
{ params: { range } },
|
})
|
||||||
)
|
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
...QUERY_STRATEGIES.frequent,
|
...QUERY_STRATEGIES.frequent,
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { QUERY_KEYS, API_ENDPOINTS, PAGINATION } from '@/config'
|
import type { ThreatEvent, ThreatList } from '@/api/types'
|
||||||
|
import { API_ENDPOINTS, PAGINATION, QUERY_KEYS } from '@/config'
|
||||||
import { apiClient, QUERY_STRATEGIES } from '@/core/api'
|
import { apiClient, QUERY_STRATEGIES } from '@/core/api'
|
||||||
import type { ThreatList, ThreatEvent } from '@/api/types'
|
|
||||||
|
|
||||||
interface ThreatParams {
|
interface ThreatParams {
|
||||||
limit?: number
|
limit?: number
|
||||||
|
|
@ -29,7 +29,7 @@ export function useThreats(params: ThreatParams = {}) {
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiClient.get<ThreatList>(
|
const { data } = await apiClient.get<ThreatList>(
|
||||||
API_ENDPOINTS.THREATS.LIST,
|
API_ENDPOINTS.THREATS.LIST,
|
||||||
{ params: queryParams },
|
{ params: queryParams }
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|
@ -42,7 +42,7 @@ export function useThreat(id: string | null) {
|
||||||
queryKey: QUERY_KEYS.THREATS.BY_ID(id ?? ''),
|
queryKey: QUERY_KEYS.THREATS.BY_ID(id ?? ''),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiClient.get<ThreatEvent>(
|
const { data } = await apiClient.get<ThreatEvent>(
|
||||||
API_ENDPOINTS.THREATS.BY_ID(id!),
|
API_ENDPOINTS.THREATS.BY_ID(id as string)
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// index.ts
|
// index.ts
|
||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
export * from './threats.types'
|
|
||||||
export * from './stats.types'
|
|
||||||
export * from './models.types'
|
export * from './models.types'
|
||||||
|
export * from './stats.types'
|
||||||
|
export * from './threats.types'
|
||||||
export * from './websocket.types'
|
export * from './websocket.types'
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import type { WebSocketAlert } from '@/api/types'
|
import type { WebSocketAlert } from '@/api/types'
|
||||||
import { SeverityBadge } from './severity-badge'
|
|
||||||
import styles from './alert-feed.module.scss'
|
import styles from './alert-feed.module.scss'
|
||||||
|
import { SeverityBadge } from './severity-badge'
|
||||||
|
|
||||||
interface AlertFeedProps {
|
interface AlertFeedProps {
|
||||||
alerts: WebSocketAlert[]
|
alerts: WebSocketAlert[]
|
||||||
|
|
@ -25,11 +25,13 @@ export function AlertFeed({
|
||||||
}: AlertFeedProps): React.ReactElement {
|
}: AlertFeedProps): React.ReactElement {
|
||||||
const listRef = useRef<HTMLDivElement>(null)
|
const listRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const alertCount = alerts.length
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: scroll on new alerts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (listRef.current) {
|
if (listRef.current) {
|
||||||
listRef.current.scrollTop = 0
|
listRef.current.scrollTop = 0
|
||||||
}
|
}
|
||||||
}, [alerts.length])
|
}, [alertCount])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.feed}>
|
<div className={styles.feed}>
|
||||||
|
|
@ -50,9 +52,7 @@ export function AlertFeed({
|
||||||
) : (
|
) : (
|
||||||
alerts.map((alert, i) => (
|
alerts.map((alert, i) => (
|
||||||
<div key={`${alert.timestamp}-${i}`} className={styles.row}>
|
<div key={`${alert.timestamp}-${i}`} className={styles.row}>
|
||||||
<span className={styles.time}>
|
<span className={styles.time}>{formatTime(alert.timestamp)}</span>
|
||||||
{formatTime(alert.timestamp)}
|
|
||||||
</span>
|
|
||||||
<span className={styles.ip}>{alert.source_ip}</span>
|
<span className={styles.ip}>{alert.source_ip}</span>
|
||||||
<span className={styles.path}>{alert.request_path}</span>
|
<span className={styles.path}>{alert.request_path}</span>
|
||||||
<SeverityBadge
|
<SeverityBadge
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// index.tsx
|
// index.tsx
|
||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
|
export { AlertFeed } from './alert-feed'
|
||||||
export { SeverityBadge } from './severity-badge'
|
export { SeverityBadge } from './severity-badge'
|
||||||
export { StatCard } from './stat-card'
|
export { StatCard } from './stat-card'
|
||||||
export { AlertFeed } from './alert-feed'
|
|
||||||
export { ThreatDetail } from './threat-detail'
|
export { ThreatDetail } from './threat-detail'
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,15 @@ export function ThreatDetail({
|
||||||
if (!threat) return null
|
if (!threat) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.overlay} onClick={onClose} onKeyDown={() => {}}>
|
// biome-ignore lint/a11y/noStaticElementInteractions: modal overlay dismiss
|
||||||
|
<div
|
||||||
|
role="presentation"
|
||||||
|
className={styles.overlay}
|
||||||
|
onClick={onClose}
|
||||||
|
onKeyDown={() => {}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
|
role="dialog"
|
||||||
className={styles.panel}
|
className={styles.panel}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onKeyDown={() => {}}
|
onKeyDown={() => {}}
|
||||||
|
|
@ -80,9 +87,7 @@ export function ThreatDetail({
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
<span className={styles.fieldLabel}>Method</span>
|
<span className={styles.fieldLabel}>Method</span>
|
||||||
<span className={styles.fieldValue}>
|
<span className={styles.fieldValue}>{threat.request_method}</span>
|
||||||
{threat.request_method}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
<span className={styles.fieldLabel}>Path</span>
|
<span className={styles.fieldLabel}>Path</span>
|
||||||
|
|
@ -90,9 +95,7 @@ export function ThreatDetail({
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
<span className={styles.fieldLabel}>Status</span>
|
<span className={styles.fieldLabel}>Status</span>
|
||||||
<span className={styles.fieldValue}>
|
<span className={styles.fieldValue}>{threat.status_code}</span>
|
||||||
{threat.status_code}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
<span className={styles.fieldLabel}>Response Size</span>
|
<span className={styles.fieldLabel}>Response Size</span>
|
||||||
|
|
@ -131,16 +134,12 @@ export function ThreatDetail({
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
<span className={styles.fieldLabel}>Country</span>
|
<span className={styles.fieldLabel}>Country</span>
|
||||||
<span className={styles.fieldValue}>
|
<span className={styles.fieldValue}>{threat.geo.country}</span>
|
||||||
{threat.geo.country}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{threat.geo.city && (
|
{threat.geo.city && (
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
<span className={styles.fieldLabel}>City</span>
|
<span className={styles.fieldLabel}>City</span>
|
||||||
<span className={styles.fieldValue}>
|
<span className={styles.fieldValue}>{threat.geo.city}</span>
|
||||||
{threat.geo.city}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,11 @@ export const QUERY_KEYS = {
|
||||||
ALL: ['threats'] as const,
|
ALL: ['threats'] as const,
|
||||||
LIST: (params: Record<string, unknown>) =>
|
LIST: (params: Record<string, unknown>) =>
|
||||||
[...QUERY_KEYS.THREATS.ALL, 'list', params] as const,
|
[...QUERY_KEYS.THREATS.ALL, 'list', params] as const,
|
||||||
BY_ID: (id: string) =>
|
BY_ID: (id: string) => [...QUERY_KEYS.THREATS.ALL, 'detail', id] as const,
|
||||||
[...QUERY_KEYS.THREATS.ALL, 'detail', id] as const,
|
|
||||||
},
|
},
|
||||||
STATS: {
|
STATS: {
|
||||||
ALL: ['stats'] as const,
|
ALL: ['stats'] as const,
|
||||||
BY_RANGE: (range: string) =>
|
BY_RANGE: (range: string) => [...QUERY_KEYS.STATS.ALL, range] as const,
|
||||||
[...QUERY_KEYS.STATS.ALL, range] as const,
|
|
||||||
},
|
},
|
||||||
MODELS: {
|
MODELS: {
|
||||||
ALL: ['models'] as const,
|
ALL: ['models'] as const,
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,5 @@ apiClient.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error: AxiosError): Promise<never> => {
|
(error: AxiosError): Promise<never> => {
|
||||||
return Promise.reject(transformAxiosError(error))
|
return Promise.reject(transformAxiosError(error))
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export class ApiError extends Error {
|
||||||
message: string,
|
message: string,
|
||||||
code: ApiErrorCode,
|
code: ApiErrorCode,
|
||||||
statusCode: number,
|
statusCode: number,
|
||||||
details?: Record<string, string[]>,
|
details?: Record<string, string[]>
|
||||||
) {
|
) {
|
||||||
super(message)
|
super(message)
|
||||||
this.name = 'ApiError'
|
this.name = 'ApiError'
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const calculateRetryDelay = (attemptIndex: number): number => {
|
||||||
|
|
||||||
const handleQueryCacheError = (
|
const handleQueryCacheError = (
|
||||||
error: Error,
|
error: Error,
|
||||||
query: { state: { data: unknown } },
|
query: { state: { data: unknown } }
|
||||||
): void => {
|
): void => {
|
||||||
if (query.state.data !== undefined) {
|
if (query.state.data !== undefined) {
|
||||||
const message =
|
const message =
|
||||||
|
|
@ -45,7 +45,7 @@ const handleMutationCacheError = (
|
||||||
error: Error,
|
error: Error,
|
||||||
_variables: unknown,
|
_variables: unknown,
|
||||||
_context: unknown,
|
_context: unknown,
|
||||||
mutation: { options: { onError?: unknown } },
|
mutation: { options: { onError?: unknown } }
|
||||||
): void => {
|
): void => {
|
||||||
if (mutation.options.onError === undefined) {
|
if (mutation.options.onError === undefined) {
|
||||||
const message =
|
const message =
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// routers.tsx
|
// routers.tsx
|
||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
import { Navigate, createBrowserRouter, type RouteObject } from 'react-router-dom'
|
import { createBrowserRouter, Navigate, type RouteObject } from 'react-router-dom'
|
||||||
import { ROUTES } from '@/config'
|
import { ROUTES } from '@/config'
|
||||||
import { Shell } from './shell'
|
import { Shell } from './shell'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,14 @@
|
||||||
|
|
||||||
import { Suspense } from 'react'
|
import { Suspense } from 'react'
|
||||||
import { ErrorBoundary } from 'react-error-boundary'
|
import { ErrorBoundary } from 'react-error-boundary'
|
||||||
import { LuChevronLeft, LuChevronRight, LuCpu, LuLayoutDashboard, LuMenu, LuShield } from 'react-icons/lu'
|
import {
|
||||||
|
LuChevronLeft,
|
||||||
|
LuChevronRight,
|
||||||
|
LuCpu,
|
||||||
|
LuLayoutDashboard,
|
||||||
|
LuMenu,
|
||||||
|
LuShield,
|
||||||
|
} from 'react-icons/lu'
|
||||||
import { NavLink, Outlet, useLocation } from 'react-router-dom'
|
import { NavLink, Outlet, useLocation } from 'react-router-dom'
|
||||||
import { ROUTES } from '@/config'
|
import { ROUTES } from '@/config'
|
||||||
import { useUIStore } from '@/core/lib'
|
import { useUIStore } from '@/core/lib'
|
||||||
|
|
@ -17,11 +24,12 @@ const NAV_ITEMS = [
|
||||||
{ path: ROUTES.MODELS, label: 'Models', icon: LuCpu },
|
{ path: ROUTES.MODELS, label: 'Models', icon: LuCpu },
|
||||||
]
|
]
|
||||||
|
|
||||||
function ShellErrorFallback({ error }: { error: Error }): React.ReactElement {
|
function ShellErrorFallback({ error }: { error: unknown }): React.ReactElement {
|
||||||
|
const message = error instanceof Error ? error.message : String(error)
|
||||||
return (
|
return (
|
||||||
<div className={styles.error}>
|
<div className={styles.error}>
|
||||||
<h2>Something went wrong</h2>
|
<h2>Something went wrong</h2>
|
||||||
<pre>{error.message}</pre>
|
<pre>{message}</pre>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
// index.tsx
|
// index.tsx
|
||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
import { useStats, useModelStatus, useAlerts } from '@/api/hooks'
|
import { useAlerts, useModelStatus, useStats } from '@/api/hooks'
|
||||||
import { StatCard, AlertFeed } from '@/components'
|
import { AlertFeed, StatCard } from '@/components'
|
||||||
import styles from './dashboard.module.scss'
|
import styles from './dashboard.module.scss'
|
||||||
|
|
||||||
function SeverityBar({
|
function SeverityBar({
|
||||||
|
|
@ -106,14 +106,8 @@ export function Component(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className={styles.page}>
|
<div className={styles.page}>
|
||||||
<div className={styles.statRow}>
|
<div className={styles.statRow}>
|
||||||
<StatCard
|
<StatCard label="Threats Detected" value={stats.threats_detected} />
|
||||||
label="Threats Detected"
|
<StatCard label="Threats Stored" value={stats.threats_stored} />
|
||||||
value={stats.threats_detected}
|
|
||||||
/>
|
|
||||||
<StatCard
|
|
||||||
label="Threats Stored"
|
|
||||||
value={stats.threats_stored}
|
|
||||||
/>
|
|
||||||
<StatCard
|
<StatCard
|
||||||
label="High Severity"
|
label="High Severity"
|
||||||
value={sb.high}
|
value={sb.high}
|
||||||
|
|
@ -122,9 +116,7 @@ export function Component(): React.ReactElement {
|
||||||
<StatCard
|
<StatCard
|
||||||
label="Detection Mode"
|
label="Detection Mode"
|
||||||
value={modelStatus?.detection_mode ?? '...'}
|
value={modelStatus?.detection_mode ?? '...'}
|
||||||
sublabel={
|
sublabel={modelStatus?.models_loaded ? 'Models loaded' : 'Rules only'}
|
||||||
modelStatus?.models_loaded ? 'Models loaded' : 'Rules only'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -134,11 +126,7 @@ export function Component(): React.ReactElement {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.bottomRow}>
|
<div className={styles.bottomRow}>
|
||||||
<AlertFeed
|
<AlertFeed alerts={alerts} isConnected={isConnected} maxHeight="360px" />
|
||||||
alerts={alerts}
|
|
||||||
isConnected={isConnected}
|
|
||||||
maxHeight="360px"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.lists}>
|
<div className={styles.lists}>
|
||||||
<RankedList
|
<RankedList
|
||||||
|
|
@ -158,9 +146,7 @@ export function Component(): React.ReactElement {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{connectionError && (
|
{connectionError && <div className={styles.wsError}>{connectionError}</div>}
|
||||||
<div className={styles.wsError}>{connectionError}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ function ModelCard({ model }: { model: ActiveModel }): React.ReactElement {
|
||||||
{model.threshold !== null && (
|
{model.threshold !== null && (
|
||||||
<div className={styles.stat}>
|
<div className={styles.stat}>
|
||||||
<span className={styles.statLabel}>Threshold</span>
|
<span className={styles.statLabel}>Threshold</span>
|
||||||
<span className={styles.statValue}>
|
<span className={styles.statValue}>{model.threshold.toFixed(4)}</span>
|
||||||
{model.threshold.toFixed(4)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -77,9 +75,7 @@ export function Component(): React.ReactElement {
|
||||||
disabled={retrain.isPending}
|
disabled={retrain.isPending}
|
||||||
onClick={() => retrain.mutate()}
|
onClick={() => retrain.mutate()}
|
||||||
>
|
>
|
||||||
<LuRefreshCw
|
<LuRefreshCw className={retrain.isPending ? styles.spinning : ''} />
|
||||||
className={retrain.isPending ? styles.spinning : ''}
|
|
||||||
/>
|
|
||||||
{retrain.isPending ? 'Retraining...' : 'Retrain Models'}
|
{retrain.isPending ? 'Retraining...' : 'Retrain Models'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -121,9 +121,7 @@ export function Component(): React.ReactElement {
|
||||||
|
|
||||||
<div className={styles.pagination}>
|
<div className={styles.pagination}>
|
||||||
<span className={styles.paginationInfo}>
|
<span className={styles.paginationInfo}>
|
||||||
{total === 0
|
{total === 0 ? 'No results' : `${offset + 1}–${showing} of ${total}`}
|
||||||
? 'No results'
|
|
||||||
: `${offset + 1}–${showing} of ${total}`}
|
|
||||||
</span>
|
</span>
|
||||||
<div className={styles.paginationButtons}>
|
<div className={styles.paginationButtons}>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, path.resolve(__dirname, '..'), '')
|
const env = loadEnv(mode, path.resolve(__dirname, '..'), '')
|
||||||
const isDev = mode === 'development'
|
const isDev = mode === 'development'
|
||||||
const apiTarget =
|
const apiTarget =
|
||||||
process.env.VITE_API_TARGET ||
|
process.env.VITE_API_TARGET || env.VITE_API_TARGET || 'http://localhost:8000'
|
||||||
env.VITE_API_TARGET ||
|
|
||||||
'http://localhost:8000'
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [react(), tsconfigPaths()],
|
plugins: [react(), tsconfigPaths()],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue