Update front for sql monitoring

This commit is contained in:
2025-10-10 02:21:59 +07:00
parent 54fc08d71a
commit de70674c0e

View File

@@ -16,42 +16,55 @@ const SqlMonitoring: React.FC = () => {
const isLoading = isLoadingStats || isLoadingAlerts || isLoadingHealth
// Prepare statistics data for table
const statisticsData = React.useMemo(() => {
if (!statistics) return []
// Prepare loop detection statistics data for table
const loopDetectionData = React.useMemo(() => {
if (!statistics?.loopDetectionStats) 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,
})
})
return Object.entries(statistics.loopDetectionStats).map(([key, stat]) => {
// Helper function to safely convert time values to milliseconds
const getTimeInMs = (timeValue: any): number => {
if (typeof timeValue === 'number') return timeValue
if (typeof timeValue === 'string') {
// Handle TimeSpan serialization from C# (e.g., "00:00:01.234")
const match = timeValue.match(/(\d+):(\d+):(\d+)\.(\d+)/)
if (match) {
const [, hours, minutes, seconds, milliseconds] = match
return (parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds)) * 1000 + parseInt(milliseconds)
}
// Try to parse as number
const parsed = parseFloat(timeValue)
return isNaN(parsed) ? 0 : parsed
}
return 0
}
// 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 {
id: key,
repository: stat.repositoryName || 'Unknown',
method: stat.methodName || 'Unknown',
queryPattern: stat.queryPattern || 'Unknown',
executionCount: Number(stat.executionCount) || 0,
executionsPerMinute: Number(stat.executionsPerMinute) || 0,
averageExecutionTime: getTimeInMs(stat.averageExecutionTime),
minExecutionTime: getTimeInMs(stat.minExecutionTime),
maxExecutionTime: getTimeInMs(stat.maxExecutionTime),
firstExecution: stat.firstExecution || '',
lastExecution: stat.lastExecution || '',
isActive: Boolean(stat.isActive),
}
})
}, [statistics])
return stats
// Prepare context statistics data for table
const contextStatsData = React.useMemo(() => {
if (!statistics?.contextStats) return []
return Object.entries(statistics.contextStats).map(([queryPattern, count]) => ({
id: queryPattern,
queryPattern: queryPattern.length > 50 ? `${queryPattern.substring(0, 50)}...` : queryPattern,
executionCount: count,
timestamp: statistics.timestamp,
}))
}, [statistics])
// Prepare alerts data for table
@@ -68,30 +81,132 @@ const SqlMonitoring: React.FC = () => {
}))
}, [alerts])
// Define columns for statistics table
const statisticsColumns = React.useMemo(
// Define columns for loop detection table
const loopDetectionColumns = React.useMemo(
() => [
{
Header: 'Type',
accessor: 'type',
Header: 'Repository',
accessor: 'repository',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Key',
accessor: 'key',
Header: 'Method',
accessor: 'method',
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Value',
accessor: 'value',
Header: 'Query Pattern',
accessor: 'queryPattern',
Cell: ({ value }: { value: string }) => (
<div className="max-w-xs truncate" title={value}>
{value}
</div>
),
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Executions',
accessor: 'executionCount',
Cell: ({ value }: { value: number }) => {
const numValue = Number(value) || 0
return (
<span className="badge badge-primary">{numValue}</span>
)
},
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Per Minute',
accessor: 'executionsPerMinute',
Cell: ({ value }: { value: number }) => {
const numValue = Number(value) || 0
return (
<span className={`badge ${numValue > 20 ? 'badge-warning' : 'badge-info'}`}>
{numValue.toFixed(1)}
</span>
)
},
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Avg Time (ms)',
accessor: 'averageExecutionTime',
Cell: ({ value }: { value: number }) => {
const numValue = Number(value) || 0
return (
<span className={`badge ${numValue > 1000 ? 'badge-error' : numValue > 500 ? 'badge-warning' : 'badge-success'}`}>
{numValue.toFixed(0)}
</span>
)
},
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Status',
accessor: 'isActive',
Cell: ({ value }: { value: boolean }) => (
<span className={`badge ${value ? 'badge-success' : 'badge-neutral'}`}>
{value ? 'Active' : 'Inactive'}
</span>
),
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Last Execution',
accessor: 'lastExecution',
Cell: ({ value }: { value: string }) => (
<span className="text-xs">
{value ? new Date(value).toLocaleString() : 'N/A'}
</span>
),
disableSortBy: true,
disableFilters: true,
},
],
[]
)
// Define columns for context stats table
const contextStatsColumns = React.useMemo(
() => [
{
Header: 'Query Pattern',
accessor: 'queryPattern',
Cell: ({ value }: { value: string }) => (
<div className="max-w-md truncate font-mono text-xs" title={value}>
{value}
</div>
),
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Execution Count',
accessor: 'executionCount',
Cell: ({ value }: { value: number }) => {
const numValue = Number(value) || 0
return (
<span className="badge badge-secondary">{numValue}</span>
)
},
disableSortBy: true,
disableFilters: true,
},
{
Header: 'Timestamp',
accessor: 'timestamp',
Cell: ({ value }: { value: string }) => (
<span className="text-xs">
{new Date(value).toLocaleString()}
</span>
),
disableSortBy: true,
disableFilters: true,
},
@@ -279,22 +394,52 @@ const SqlMonitoring: React.FC = () => {
</div>
</div>
{/* Statistics Section */}
{/* Loop Detection Statistics */}
<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" />
<h3 className="card-title text-lg">Loop Detection Statistics</h3>
<p className="text-sm text-base-content/70 mb-4">
Detailed performance metrics for tracked queries with loop detection
</p>
{loopDetectionData.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 statistics available yet</span>
<span>No loop detection data available yet</span>
</div>
) : (
<div className="overflow-x-auto">
<Table
columns={statisticsColumns}
data={statisticsData}
columns={loopDetectionColumns}
data={loopDetectionData}
showPagination={true}
showTotal={false}
/>
</div>
)}
</div>
</div>
{/* Context Statistics */}
<div className="card bg-base-200">
<div className="card-body">
<h3 className="card-title text-lg">Query Execution Counts</h3>
<p className="text-sm text-base-content/70 mb-4">
Simple execution count tracking for all query patterns
</p>
{contextStatsData.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 context statistics available yet</span>
</div>
) : (
<div className="overflow-x-auto">
<Table
columns={contextStatsColumns}
data={contextStatsData}
showPagination={true}
showTotal={false}
/>