feat: add React Query hooks for admin infrastructure endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
22
ui/src/api/queries/admin/admin-api.ts
Normal file
22
ui/src/api/queries/admin/admin-api.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { config } from '../../../config';
|
||||||
|
import { useAuthStore } from '../../../auth/auth-store';
|
||||||
|
|
||||||
|
export async function adminFetch<T>(path: string, options?: RequestInit): Promise<T> {
|
||||||
|
const token = useAuthStore.getState().accessToken;
|
||||||
|
const res = await fetch(`${config.apiBaseUrl}/admin${path}`, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
'X-Cameleer-Protocol-Version': '1',
|
||||||
|
...options?.headers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
useAuthStore.getState().logout();
|
||||||
|
throw new Error('Unauthorized');
|
||||||
|
}
|
||||||
|
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
||||||
|
if (res.status === 204) return undefined as T;
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
45
ui/src/api/queries/admin/audit.ts
Normal file
45
ui/src/api/queries/admin/audit.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { adminFetch } from './admin-api';
|
||||||
|
|
||||||
|
export interface AuditEvent {
|
||||||
|
id: string;
|
||||||
|
timestamp: string;
|
||||||
|
username: string;
|
||||||
|
category: string;
|
||||||
|
action: string;
|
||||||
|
target: string;
|
||||||
|
result: string;
|
||||||
|
detail: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditLogParams {
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
username?: string;
|
||||||
|
category?: string;
|
||||||
|
search?: string;
|
||||||
|
page?: number;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditLogResponse {
|
||||||
|
events: AuditEvent[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuditLog(params: AuditLogParams) {
|
||||||
|
const query = new URLSearchParams();
|
||||||
|
if (params.from) query.set('from', params.from);
|
||||||
|
if (params.to) query.set('to', params.to);
|
||||||
|
if (params.username) query.set('username', params.username);
|
||||||
|
if (params.category) query.set('category', params.category);
|
||||||
|
if (params.search) query.set('search', params.search);
|
||||||
|
if (params.page !== undefined) query.set('page', String(params.page));
|
||||||
|
if (params.size !== undefined) query.set('size', String(params.size));
|
||||||
|
const qs = query.toString();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'audit', params],
|
||||||
|
queryFn: () => adminFetch<AuditLogResponse>(`/audit${qs ? `?${qs}` : ''}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
71
ui/src/api/queries/admin/database.ts
Normal file
71
ui/src/api/queries/admin/database.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { adminFetch } from './admin-api';
|
||||||
|
|
||||||
|
export interface DatabaseStatus {
|
||||||
|
connected: boolean;
|
||||||
|
version: string;
|
||||||
|
host: string;
|
||||||
|
schema: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PoolStats {
|
||||||
|
activeConnections: number;
|
||||||
|
idleConnections: number;
|
||||||
|
pendingConnections: number;
|
||||||
|
maxConnections: number;
|
||||||
|
maxWaitMillis: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableInfo {
|
||||||
|
tableName: string;
|
||||||
|
rowEstimate: number;
|
||||||
|
dataSize: string;
|
||||||
|
indexSize: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActiveQuery {
|
||||||
|
pid: number;
|
||||||
|
durationMs: number;
|
||||||
|
state: string;
|
||||||
|
query: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDatabaseStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'database', 'status'],
|
||||||
|
queryFn: () => adminFetch<DatabaseStatus>('/database/status'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDatabasePool() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'database', 'pool'],
|
||||||
|
queryFn: () => adminFetch<PoolStats>('/database/pool'),
|
||||||
|
refetchInterval: 15000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDatabaseTables() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'database', 'tables'],
|
||||||
|
queryFn: () => adminFetch<TableInfo[]>('/database/tables'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDatabaseQueries() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'database', 'queries'],
|
||||||
|
queryFn: () => adminFetch<ActiveQuery[]>('/database/queries'),
|
||||||
|
refetchInterval: 15000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useKillQuery() {
|
||||||
|
const qc = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (pid: number) => {
|
||||||
|
await adminFetch<void>(`/database/queries/${pid}`, { method: 'DELETE' });
|
||||||
|
},
|
||||||
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['admin', 'database', 'queries'] }),
|
||||||
|
});
|
||||||
|
}
|
||||||
102
ui/src/api/queries/admin/opensearch.ts
Normal file
102
ui/src/api/queries/admin/opensearch.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { adminFetch } from './admin-api';
|
||||||
|
|
||||||
|
export interface OpenSearchStatus {
|
||||||
|
connected: boolean;
|
||||||
|
clusterName: string;
|
||||||
|
clusterHealth: string;
|
||||||
|
version: string;
|
||||||
|
numberOfNodes: number;
|
||||||
|
host: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PipelineStats {
|
||||||
|
queueDepth: number;
|
||||||
|
maxQueueSize: number;
|
||||||
|
totalIndexed: number;
|
||||||
|
totalFailed: number;
|
||||||
|
avgLatencyMs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IndexInfo {
|
||||||
|
name: string;
|
||||||
|
health: string;
|
||||||
|
status: string;
|
||||||
|
docsCount: number;
|
||||||
|
storeSize: string;
|
||||||
|
primaryShards: number;
|
||||||
|
replicas: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PerformanceStats {
|
||||||
|
queryCacheHitRate: number;
|
||||||
|
requestCacheHitRate: number;
|
||||||
|
avgQueryLatencyMs: number;
|
||||||
|
avgIndexLatencyMs: number;
|
||||||
|
jvmHeapUsedPercent: number;
|
||||||
|
jvmHeapUsedBytes: number;
|
||||||
|
jvmHeapMaxBytes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IndicesParams {
|
||||||
|
search?: string;
|
||||||
|
health?: string;
|
||||||
|
sortBy?: string;
|
||||||
|
sortDir?: 'asc' | 'desc';
|
||||||
|
page?: number;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOpenSearchStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'opensearch', 'status'],
|
||||||
|
queryFn: () => adminFetch<OpenSearchStatus>('/opensearch/status'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePipelineStats() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'opensearch', 'pipeline'],
|
||||||
|
queryFn: () => adminFetch<PipelineStats>('/opensearch/pipeline'),
|
||||||
|
refetchInterval: 15000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIndices(params: IndicesParams) {
|
||||||
|
const query = new URLSearchParams();
|
||||||
|
if (params.search) query.set('search', params.search);
|
||||||
|
if (params.health) query.set('health', params.health);
|
||||||
|
if (params.sortBy) query.set('sortBy', params.sortBy);
|
||||||
|
if (params.sortDir) query.set('sortDir', params.sortDir);
|
||||||
|
if (params.page !== undefined) query.set('page', String(params.page));
|
||||||
|
if (params.size !== undefined) query.set('size', String(params.size));
|
||||||
|
const qs = query.toString();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'opensearch', 'indices', params],
|
||||||
|
queryFn: () =>
|
||||||
|
adminFetch<{ indices: IndexInfo[]; total: number }>(
|
||||||
|
`/opensearch/indices${qs ? `?${qs}` : ''}`,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePerformanceStats() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'opensearch', 'performance'],
|
||||||
|
queryFn: () => adminFetch<PerformanceStats>('/opensearch/performance'),
|
||||||
|
refetchInterval: 15000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDeleteIndex() {
|
||||||
|
const qc = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (indexName: string) => {
|
||||||
|
await adminFetch<void>(`/opensearch/indices/${encodeURIComponent(indexName)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['admin', 'opensearch', 'indices'] }),
|
||||||
|
});
|
||||||
|
}
|
||||||
33
ui/src/api/queries/admin/thresholds.ts
Normal file
33
ui/src/api/queries/admin/thresholds.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { adminFetch } from './admin-api';
|
||||||
|
|
||||||
|
export interface Thresholds {
|
||||||
|
poolWarningPercent: number;
|
||||||
|
poolCriticalPercent: number;
|
||||||
|
queryDurationWarningSeconds: number;
|
||||||
|
queryDurationCriticalSeconds: number;
|
||||||
|
osQueueWarningPercent: number;
|
||||||
|
osQueueCriticalPercent: number;
|
||||||
|
osHeapWarningPercent: number;
|
||||||
|
osHeapCriticalPercent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useThresholds() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['admin', 'thresholds'],
|
||||||
|
queryFn: () => adminFetch<Thresholds>('/thresholds'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSaveThresholds() {
|
||||||
|
const qc = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (body: Thresholds) => {
|
||||||
|
await adminFetch<Thresholds>('/thresholds', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['admin', 'thresholds'] }),
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user