Add monitoring on queries with sentry alert + Fix check position list in db for backtest

This commit is contained in:
2025-10-10 00:15:02 +07:00
parent ffb98fe359
commit e4c2f8b7a5
24 changed files with 3340 additions and 179 deletions

View File

@@ -48,7 +48,7 @@ const LogIn = () => {
.user_CreateToken({
address: walletAddress,
message: message,
name: form.name,
name: user?.id,
signature: signature,
})
.then((data) => {
@@ -101,19 +101,6 @@ const LogIn = () => {
action="#"
onSubmit={handleSubmit(onSubmit)}
>
<div>
<label
htmlFor="name"
className="dark:text-white block mb-2 text-sm font-medium text-gray-900"
hidden={true}
>
Name
</label>
<input
className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
{...register('name')}
></input>
</div>
<button
type="submit"
className="btn bg-primary w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"

View File

@@ -69,7 +69,7 @@ export default function Table({
) as TableInstanceWithHooks<any>
// Calculez le total des valeurs dans la colonne USD
const total = data
const total = data && showTotal
? data
.reduce((sum: number, row: any) => {
return sum + (row.value || 0) // Si la valeur est undefined = 0

View File

@@ -3062,6 +3062,224 @@ export class SettingsClient extends AuthorizedApiBase {
}
}
export class SqlMonitoringClient extends AuthorizedApiBase {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
super(configuration);
this.http = http ? http : window as any;
this.baseUrl = baseUrl ?? "http://localhost:5000";
}
sqlMonitoring_GetQueryStatistics(): Promise<FileResponse> {
let url_ = this.baseUrl + "/api/SqlMonitoring/statistics";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/octet-stream"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processSqlMonitoring_GetQueryStatistics(_response);
});
}
protected processSqlMonitoring_GetQueryStatistics(response: Response): Promise<FileResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
if (fileName) {
fileName = decodeURIComponent(fileName);
} else {
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
}
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse>(null as any);
}
sqlMonitoring_GetAlerts(): Promise<FileResponse> {
let url_ = this.baseUrl + "/api/SqlMonitoring/alerts";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/octet-stream"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processSqlMonitoring_GetAlerts(_response);
});
}
protected processSqlMonitoring_GetAlerts(response: Response): Promise<FileResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
if (fileName) {
fileName = decodeURIComponent(fileName);
} else {
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
}
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse>(null as any);
}
sqlMonitoring_ClearTracking(): Promise<FileResponse> {
let url_ = this.baseUrl + "/api/SqlMonitoring/clear-tracking";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "POST",
headers: {
"Accept": "application/octet-stream"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processSqlMonitoring_ClearTracking(_response);
});
}
protected processSqlMonitoring_ClearTracking(response: Response): Promise<FileResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
if (fileName) {
fileName = decodeURIComponent(fileName);
} else {
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
}
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse>(null as any);
}
sqlMonitoring_GetQueryDetails(repositoryName: string, methodName: string): Promise<FileResponse> {
let url_ = this.baseUrl + "/api/SqlMonitoring/query-details/{repositoryName}/{methodName}";
if (repositoryName === undefined || repositoryName === null)
throw new Error("The parameter 'repositoryName' must be defined.");
url_ = url_.replace("{repositoryName}", encodeURIComponent("" + repositoryName));
if (methodName === undefined || methodName === null)
throw new Error("The parameter 'methodName' must be defined.");
url_ = url_.replace("{methodName}", encodeURIComponent("" + methodName));
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/octet-stream"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processSqlMonitoring_GetQueryDetails(_response);
});
}
protected processSqlMonitoring_GetQueryDetails(response: Response): Promise<FileResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
if (fileName) {
fileName = decodeURIComponent(fileName);
} else {
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
}
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse>(null as any);
}
sqlMonitoring_GetMonitoringHealth(): Promise<FileResponse> {
let url_ = this.baseUrl + "/api/SqlMonitoring/health";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/octet-stream"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processSqlMonitoring_GetMonitoringHealth(_response);
});
}
protected processSqlMonitoring_GetMonitoringHealth(response: Response): Promise<FileResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
if (fileName) {
fileName = decodeURIComponent(fileName);
} else {
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
}
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse>(null as any);
}
}
export class TradingClient extends AuthorizedApiBase {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;

View File

@@ -0,0 +1,182 @@
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import useApiUrlStore from '../app/store/apiStore'
import {SqlMonitoringClient} from '../generated/ManagingApi'
// Interface for SQL monitoring statistics
interface SqlMonitoringStats {
loopDetectionStats: Record<string, any>
contextStats: Record<string, any>
timestamp: string
totalTrackedQueries: number
activeQueries: number
}
// Interface for SQL monitoring alerts
interface SqlMonitoringAlert {
id: string
type: string
message: string
timestamp: string
repository: string
method: string
severity: string
}
// Interface for monitoring health
interface MonitoringHealth {
isEnabled: boolean
loggingEnabled: boolean
sentryEnabled: boolean
loopDetectionEnabled: boolean
performanceMonitoringEnabled: boolean
lastHealthCheck: string
totalAlerts: number
activeQueries: number
}
// Interface for query details
interface QueryDetail {
repository: string
method: string
queryPattern: string
executionCount: number
averageExecutionTime: number
lastExecution: string
isActive: boolean
}
// Hook for SQL monitoring statistics
export const useSqlMonitoringStats = () => {
const { apiUrl } = useApiUrlStore()
const sqlMonitoringClient = new SqlMonitoringClient({}, apiUrl)
return useQuery({
queryKey: ['sqlMonitoring', 'statistics'],
queryFn: async () => {
try {
const response = await sqlMonitoringClient.sqlMonitoring_GetQueryStatistics()
const text = await response.data.text()
const data = JSON.parse(text) as SqlMonitoringStats
// Ensure the data has the expected structure
return {
loopDetectionStats: data.loopDetectionStats || {},
contextStats: data.contextStats || {},
timestamp: data.timestamp || new Date().toISOString(),
totalTrackedQueries: data.totalTrackedQueries || 0,
activeQueries: data.activeQueries || 0,
}
} catch (error) {
console.error('Error fetching SQL monitoring statistics:', error)
throw error
}
},
refetchInterval: 30000, // Refresh every 30 seconds
})
}
// Hook for SQL monitoring alerts
export const useSqlMonitoringAlerts = () => {
const { apiUrl } = useApiUrlStore()
const sqlMonitoringClient = new SqlMonitoringClient({}, apiUrl)
return useQuery({
queryKey: ['sqlMonitoring', 'alerts'],
queryFn: async () => {
try {
const response = await sqlMonitoringClient.sqlMonitoring_GetAlerts()
const text = await response.data.text()
const data = JSON.parse(text) as SqlMonitoringAlert[]
// Ensure we return an array
return Array.isArray(data) ? data : []
} catch (error) {
console.error('Error fetching SQL monitoring alerts:', error)
throw error
}
},
refetchInterval: 15000, // Refresh every 15 seconds
})
}
// Hook for monitoring health
export const useSqlMonitoringHealth = () => {
const { apiUrl } = useApiUrlStore()
const sqlMonitoringClient = new SqlMonitoringClient({}, apiUrl)
return useQuery({
queryKey: ['sqlMonitoring', 'health'],
queryFn: async () => {
try {
const response = await sqlMonitoringClient.sqlMonitoring_GetMonitoringHealth()
const text = await response.data.text()
const data = JSON.parse(text) as MonitoringHealth
// Ensure the data has the expected structure
return {
isEnabled: data.isEnabled || false,
loggingEnabled: data.loggingEnabled || false,
sentryEnabled: data.sentryEnabled || false,
loopDetectionEnabled: data.loopDetectionEnabled || false,
performanceMonitoringEnabled: data.performanceMonitoringEnabled || false,
lastHealthCheck: data.lastHealthCheck || new Date().toISOString(),
totalAlerts: data.totalAlerts || 0,
activeQueries: data.activeQueries || 0,
}
} catch (error) {
console.error('Error fetching SQL monitoring health:', error)
throw error
}
},
refetchInterval: 60000, // Refresh every minute
})
}
// Hook for query details
export const useSqlMonitoringQueryDetails = (repositoryName: string, methodName: string) => {
const { apiUrl } = useApiUrlStore()
const sqlMonitoringClient = new SqlMonitoringClient({}, apiUrl)
return useQuery({
queryKey: ['sqlMonitoring', 'queryDetails', repositoryName, methodName],
queryFn: async () => {
try {
const response = await sqlMonitoringClient.sqlMonitoring_GetQueryDetails(repositoryName, methodName)
const text = await response.data.text()
const data = JSON.parse(text) as QueryDetail[]
// Ensure we return an array
return Array.isArray(data) ? data : []
} catch (error) {
console.error('Error fetching SQL monitoring query details:', error)
throw error
}
},
enabled: !!repositoryName && !!methodName, // Only run if both parameters are provided
})
}
// Hook for clearing tracking data
export const useClearSqlMonitoringTracking = () => {
const { apiUrl } = useApiUrlStore()
const queryClient = useQueryClient()
const sqlMonitoringClient = new SqlMonitoringClient({}, apiUrl)
return useMutation({
mutationFn: async () => {
await sqlMonitoringClient.sqlMonitoring_ClearTracking()
},
onSuccess: () => {
// Invalidate all SQL monitoring queries to refresh data
queryClient.invalidateQueries({ queryKey: ['sqlMonitoring'] })
},
})
}
// Export types for use in components
export type {
SqlMonitoringStats,
SqlMonitoringAlert,
MonitoringHealth,
QueryDetail,
}

View File

@@ -9,6 +9,7 @@ import Theme from './theme'
import DefaultConfig from './defaultConfig/defaultConfig'
import UserInfoSettings from './UserInfoSettings'
import AccountFee from './accountFee/accountFee'
import SqlMonitoring from './sqlmonitoring/sqlMonitoring'
type TabsType = {
label: string
@@ -53,6 +54,11 @@ const tabs: TabsType = [
index: 7,
label: 'Health Checks',
},
{
Component: SqlMonitoring,
index: 8,
label: 'SQL Monitoring',
},
]
const Settings: React.FC = () => {

View File

@@ -0,0 +1,94 @@
# SQL Monitoring Dashboard
This component provides a comprehensive single-page dashboard for monitoring SQL query performance, loop detection, and system health in the Managing application.
## Features
### Overview Cards
- **Total Tracked Queries**: Shows the total number of queries tracked by the monitoring system
- **Active Queries**: Displays currently monitored queries
- **Total Alerts**: Shows the number of alerts generated by the system
- **Monitoring Status**: Indicates whether SQL monitoring is active or inactive
### System Health Section
- **Monitoring Status**: Overall health of the SQL monitoring system
- **Feature Status**: Individual status of monitoring features (logging, Sentry, loop detection, etc.)
- **Compact Layout**: All health indicators displayed in a responsive grid
### Recent Alerts Section
- **SQL Monitoring Alerts**: Real-time alerts for SQL performance issues
- **Severity Levels**: Critical, Warning, and Info alerts with color-coded badges
- **Repository and Method**: Shows which repository and method triggered the alert
- **Timestamp**: When the alert was generated
### Query Statistics Section
- **Query Statistics**: Detailed statistics about query execution patterns
- **Loop Detection Stats**: Information about detected query loops
- **Context Stats**: Additional context information about the monitoring system
### Information Panel
- **Usage Instructions**: Explains how the dashboard works
- **Auto-refresh Info**: Details about automatic data updates
- **Clear Data Instructions**: How to reset monitoring statistics
## API Integration
The component integrates with the following SQL monitoring endpoints:
- `GET /api/sqlmonitoring/statistics` - Get query statistics
- `GET /api/sqlmonitoring/alerts` - Get monitoring alerts
- `GET /api/sqlmonitoring/health` - Get monitoring health status
- `POST /api/sqlmonitoring/clear` - Clear tracking data
- `GET /api/sqlmonitoring/details/{repositoryName}/{methodName}` - Get query details
## Auto-refresh
- **Statistics**: Refreshes every 30 seconds
- **Alerts**: Refreshes every 15 seconds
- **Health**: Refreshes every minute
## Admin Authorization
All SQL monitoring endpoints require admin authorization. Only users with admin privileges can access this dashboard.
## Mobile-Friendly Design
The dashboard is designed to be fully responsive and mobile-friendly:
- **Responsive Grid**: Overview cards adapt from 1 column on mobile to 4 columns on desktop
- **Compact Layout**: Health status indicators are arranged in a responsive grid
- **Horizontal Scrolling**: Tables have horizontal scroll on smaller screens
- **Touch-Friendly**: All interactive elements are appropriately sized for touch devices
- **Readable Text**: Font sizes and spacing optimized for mobile viewing
## Usage
1. Navigate to Settings → SQL Monitoring
2. View all monitoring information on a single page
3. Use the "Clear Tracking Data" button to reset monitoring statistics
4. Monitor alerts and statistics in real-time
5. Check system health status at a glance
## Configuration
The SQL monitoring system can be configured via `appsettings.json`:
```json
{
"SqlMonitoring": {
"Enabled": true,
"LoggingEnabled": true,
"SentryEnabled": true,
"LoopDetectionEnabled": true,
"PerformanceMonitoringEnabled": true,
"LoopDetectionWindowSeconds": 60,
"MaxQueryExecutionsPerWindow": 100,
"MaxMethodExecutionsPerWindow": 50,
"LongRunningQueryThresholdMs": 1000,
"SentryAlertThreshold": 5,
"SlowQueryThresholdMs": 2000,
"LogSlowQueriesOnly": false,
"LogErrorsOnly": false
}
}
```

View File

@@ -0,0 +1,311 @@
import React from 'react'
import {Table} from '../../../components/mollecules'
import {
useClearSqlMonitoringTracking,
useSqlMonitoringAlerts,
useSqlMonitoringHealth,
useSqlMonitoringStats,
} from '../../../hooks/useSqlMonitoring'
const SqlMonitoring: React.FC = () => {
// Use custom hooks for SQL monitoring data
const { data: statistics, isLoading: isLoadingStats } = useSqlMonitoringStats()
const { data: alerts, isLoading: isLoadingAlerts } = useSqlMonitoringAlerts()
const { data: health, isLoading: isLoadingHealth } = useSqlMonitoringHealth()
const clearTrackingMutation = useClearSqlMonitoringTracking()
const isLoading = isLoadingStats || isLoadingAlerts || isLoadingHealth
// Prepare statistics data for table
const statisticsData = React.useMemo(() => {
if (!statistics) return []
const stats: Array<{
type: string
key: string
value: string
timestamp: string
}> = []
// Add loop detection stats
if (statistics.loopDetectionStats) {
Object.entries(statistics.loopDetectionStats).forEach(([key, value]) => {
stats.push({
type: 'Loop Detection',
key,
value: typeof value === 'object' ? JSON.stringify(value) : String(value),
timestamp: statistics.timestamp,
})
})
}
// Add context stats
if (statistics.contextStats) {
Object.entries(statistics.contextStats).forEach(([key, value]) => {
stats.push({
type: 'Context',
key,
value: String(value),
timestamp: statistics.timestamp,
})
})
}
return stats
}, [statistics])
// Prepare alerts data for table
const alertsData = React.useMemo(() => {
if (!alerts || !Array.isArray(alerts)) return []
return alerts.map(alert => ({
id: alert.id || 'unknown',
type: alert.type || 'unknown',
message: alert.message || 'No message',
timestamp: alert.timestamp || new Date().toISOString(),
repository: alert.repository || 'unknown',
method: alert.method || 'unknown',
severity: alert.severity || 'info',
}))
}, [alerts])
// Define columns for statistics table
const statisticsColumns = React.useMemo(
() => [
{
Header: 'Type',
accessor: 'type',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Key',
accessor: 'key',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Value',
accessor: 'value',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Timestamp',
accessor: 'timestamp',
disableSortBy: true,
disableFilters: true,
},
],
[]
)
// Define columns for alerts table
const alertsColumns = React.useMemo(
() => [
{
Header: 'Severity',
accessor: 'severity',
Cell: ({ value }: { value: string }) => (
<span
className={`badge ${
value === 'Critical' || value === 'Error'
? 'badge-error'
: value === 'Warning'
? 'badge-warning'
: 'badge-info'
}`}
>
{value}
</span>
),
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Type',
accessor: 'type',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Repository',
accessor: 'repository',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Method',
accessor: 'method',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Message',
accessor: 'message',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Timestamp',
accessor: 'timestamp',
disableSortBy: true,
disableFilters: true,
},
],
[]
)
return (
<div className="container mx-auto space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<h2 className="text-2xl font-bold">SQL Monitoring Dashboard</h2>
<button
className="btn btn-outline btn-sm"
onClick={() => clearTrackingMutation.mutate()}
disabled={clearTrackingMutation.isPending}
>
{clearTrackingMutation.isPending ? 'Clearing...' : 'Clear Tracking Data'}
</button>
</div>
{isLoading ? (
<div className="flex justify-center">
<progress className="progress progress-primary w-56"></progress>
</div>
) : (
<div className="space-y-6">
{/* Overview Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="stat bg-base-200 rounded-lg">
<div className="stat-title text-sm">Total Tracked Queries</div>
<div className="stat-value text-lg text-primary">
{statistics?.totalTrackedQueries || 0}
</div>
<div className="stat-desc text-xs">All time</div>
</div>
<div className="stat bg-base-200 rounded-lg">
<div className="stat-title text-sm">Active Queries</div>
<div className="stat-value text-lg text-secondary">
{statistics?.activeQueries || 0}
</div>
<div className="stat-desc text-xs">Currently monitored</div>
</div>
<div className="stat bg-base-200 rounded-lg">
<div className="stat-title text-sm">Total Alerts</div>
<div className="stat-value text-lg text-warning">
{alerts?.length || 0}
</div>
<div className="stat-desc text-xs">All alerts</div>
</div>
<div className="stat bg-base-200 rounded-lg">
<div className="stat-title text-sm">Monitoring Status</div>
<div className={`stat-value text-lg ${health?.isEnabled ? 'text-success' : 'text-error'}`}>
{health?.isEnabled ? 'Active' : 'Inactive'}
</div>
<div className="stat-desc text-xs">System status</div>
</div>
</div>
{/* Health Status */}
<div className="card bg-base-200">
<div className="card-body">
<h3 className="card-title text-lg">System Health</h3>
{health ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
<div className="flex flex-col items-center">
<span className="text-sm font-medium mb-1">Monitoring</span>
<span className={`badge ${health.isEnabled ? 'badge-success' : 'badge-error'}`}>
{health.isEnabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="flex flex-col items-center">
<span className="text-sm font-medium mb-1">Logging</span>
<span className={`badge ${health.loggingEnabled ? 'badge-success' : 'badge-error'}`}>
{health.loggingEnabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="flex flex-col items-center">
<span className="text-sm font-medium mb-1">Sentry</span>
<span className={`badge ${health.sentryEnabled ? 'badge-success' : 'badge-error'}`}>
{health.sentryEnabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="flex flex-col items-center">
<span className="text-sm font-medium mb-1">Loop Detection</span>
<span className={`badge ${health.loopDetectionEnabled ? 'badge-success' : 'badge-error'}`}>
{health.loopDetectionEnabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="flex flex-col items-center">
<span className="text-sm font-medium mb-1">Performance</span>
<span className={`badge ${health.performanceMonitoringEnabled ? 'badge-success' : 'badge-error'}`}>
{health.performanceMonitoringEnabled ? 'Enabled' : 'Disabled'}
</span>
</div>
</div>
) : (
<div className="alert alert-error">
<svg xmlns="http://www.w3.org/2000/svg" className="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>Unable to fetch monitoring health data</span>
</div>
)}
</div>
</div>
{/* Alerts Section */}
<div className="card bg-base-200">
<div className="card-body">
<h3 className="card-title text-lg">Recent Alerts</h3>
{alertsData.length === 0 ? (
<div className="alert alert-info">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="stroke-current shrink-0 w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>No alerts found. The system is running smoothly!</span>
</div>
) : (
<div className="overflow-x-auto">
<Table
columns={alertsColumns}
data={alertsData}
showPagination={true}
showTotal={false}
/>
</div>
)}
</div>
</div>
{/* Statistics Section */}
<div className="card bg-base-200">
<div className="card-body">
<h3 className="card-title text-lg">Query Statistics</h3>
{statisticsData.length === 0 ? (
<div className="alert alert-warning">
<svg xmlns="http://www.w3.org/2000/svg" className="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
<span>No statistics available yet</span>
</div>
) : (
<div className="overflow-x-auto">
<Table
columns={statisticsColumns}
data={statisticsData}
showPagination={true}
showTotal={false}
/>
</div>
)}
</div>
</div>
</div>
)}
</div>
)
}
export default SqlMonitoring