Ref #556: adjust alert history range handling
This commit is contained in:
parent
32421b36b8
commit
5927535110
3 changed files with 218 additions and 25 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { createSignal, Show, For, createMemo, createEffect, onMount } from 'solid-js';
|
||||
import { createSignal, Show, For, createMemo, createEffect, onMount, onCleanup } from 'solid-js';
|
||||
import type { JSX } from 'solid-js';
|
||||
import { EmailProviderSelect } from '@/components/Alerts/EmailProviderSelect';
|
||||
import { WebhookConfig } from '@/components/Alerts/WebhookConfig';
|
||||
|
|
@ -2936,6 +2936,54 @@ function HistoryTab() {
|
|||
(typeof navigator !== 'undefined' ? navigator.language : undefined) ||
|
||||
'en-US';
|
||||
|
||||
const buildHistoryParams = (range: string) => {
|
||||
const params: { limit?: number; startTime?: string } = {};
|
||||
const now = Date.now();
|
||||
|
||||
switch (range) {
|
||||
case '24h':
|
||||
params.limit = 2000;
|
||||
params.startTime = new Date(now - 24 * MS_PER_HOUR).toISOString();
|
||||
break;
|
||||
case '7d':
|
||||
params.limit = 10000;
|
||||
params.startTime = new Date(now - 7 * 24 * MS_PER_HOUR).toISOString();
|
||||
break;
|
||||
case '30d':
|
||||
params.limit = 10000;
|
||||
params.startTime = new Date(now - 30 * 24 * MS_PER_HOUR).toISOString();
|
||||
break;
|
||||
case 'all':
|
||||
params.limit = 0;
|
||||
break;
|
||||
default:
|
||||
params.limit = 1000;
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
let fetchRequestId = 0;
|
||||
const fetchAlertHistory = async (range: string) => {
|
||||
const requestId = ++fetchRequestId;
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const history = await AlertsAPI.getHistory(buildHistoryParams(range));
|
||||
if (requestId === fetchRequestId) {
|
||||
setAlertHistory(history);
|
||||
}
|
||||
} catch (err) {
|
||||
if (requestId === fetchRequestId) {
|
||||
console.error('Failed to load alert history:', err);
|
||||
}
|
||||
} finally {
|
||||
if (requestId === fetchRequestId) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Ref for search input
|
||||
let searchInputRef: HTMLInputElement | undefined;
|
||||
|
||||
|
|
@ -2968,15 +3016,8 @@ function HistoryTab() {
|
|||
});
|
||||
|
||||
// Load alert history on mount
|
||||
onMount(async () => {
|
||||
try {
|
||||
const history = await AlertsAPI.getHistory({ limit: 1000 });
|
||||
setAlertHistory(history);
|
||||
} catch (err) {
|
||||
console.error('Failed to load alert history:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
onMount(() => {
|
||||
fetchAlertHistory(timeFilter());
|
||||
|
||||
// Add keyboard event listeners
|
||||
const handleKeydown = (e: KeyboardEvent) => {
|
||||
|
|
@ -3004,10 +3045,21 @@ function HistoryTab() {
|
|||
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
onCleanup(() => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
};
|
||||
// Prevent pending requests from updating state after unmount
|
||||
fetchRequestId++;
|
||||
});
|
||||
});
|
||||
|
||||
let skipInitialFetchEffect = true;
|
||||
createEffect(() => {
|
||||
const range = timeFilter();
|
||||
if (skipInitialFetchEffect) {
|
||||
skipInitialFetchEffect = false;
|
||||
return;
|
||||
}
|
||||
fetchAlertHistory(range);
|
||||
});
|
||||
|
||||
// Format duration for display
|
||||
|
|
|
|||
|
|
@ -3540,6 +3540,15 @@ func (m *Manager) GetAlertHistory(limit int) []Alert {
|
|||
return m.historyManager.GetAllHistory(limit)
|
||||
}
|
||||
|
||||
// GetAlertHistorySince returns alert history entries created after the provided time.
|
||||
func (m *Manager) GetAlertHistorySince(since time.Time, limit int) []Alert {
|
||||
if since.IsZero() {
|
||||
return m.GetAlertHistory(limit)
|
||||
}
|
||||
|
||||
return m.historyManager.GetHistory(since, limit)
|
||||
}
|
||||
|
||||
// ClearAlertHistory clears all alert history
|
||||
func (m *Manager) ClearAlertHistory() error {
|
||||
return m.historyManager.ClearAllHistory()
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import (
|
|||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/websocket"
|
||||
|
|
@ -112,35 +114,165 @@ func (h *AlertHandlers) GetActiveAlerts(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
// GetAlertHistory returns alert history
|
||||
func (h *AlertHandlers) GetAlertHistory(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
|
||||
limit := 100
|
||||
if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 10000 {
|
||||
limit = l
|
||||
} else if err != nil {
|
||||
if limitStr := query.Get("limit"); limitStr != "" {
|
||||
l, err := strconv.Atoi(limitStr)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Warn().Str("limit", limitStr).Msg("Invalid limit parameter, using default")
|
||||
} else {
|
||||
case l < 0:
|
||||
http.Error(w, "limit must be non-negative", http.StatusBadRequest)
|
||||
return
|
||||
case l == 0:
|
||||
limit = 0
|
||||
case l > 10000:
|
||||
log.Warn().Int("limit", l).Msg("Limit exceeds maximum, capping at 10000")
|
||||
limit = 10000
|
||||
default:
|
||||
limit = l
|
||||
}
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if offsetStr := query.Get("offset"); offsetStr != "" {
|
||||
if o, err := strconv.Atoi(offsetStr); err == nil {
|
||||
if o < 0 {
|
||||
http.Error(w, "offset must be non-negative", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
offset = o
|
||||
} else {
|
||||
log.Warn().Str("offset", offsetStr).Msg("Invalid offset parameter, ignoring")
|
||||
}
|
||||
}
|
||||
|
||||
var startTime *time.Time
|
||||
if startStr := query.Get("startTime"); startStr != "" {
|
||||
parsed, err := time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid startTime parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
startTime = &parsed
|
||||
}
|
||||
|
||||
var endTime *time.Time
|
||||
if endStr := query.Get("endTime"); endStr != "" {
|
||||
parsed, err := time.Parse(time.RFC3339, endStr)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid endTime parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
endTime = &parsed
|
||||
}
|
||||
|
||||
if startTime != nil && endTime != nil && endTime.Before(*startTime) {
|
||||
http.Error(w, "endTime must be after startTime", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
severity := strings.ToLower(strings.TrimSpace(query.Get("severity")))
|
||||
switch severity {
|
||||
case "", "all":
|
||||
severity = ""
|
||||
case "warning", "critical":
|
||||
default:
|
||||
log.Warn().Str("severity", severity).Msg("Invalid severity filter, ignoring")
|
||||
severity = ""
|
||||
}
|
||||
|
||||
resourceID := strings.TrimSpace(query.Get("resourceId"))
|
||||
|
||||
matchesFilters := func(alertTime time.Time, alertLevel string, alertResourceID string) bool {
|
||||
if startTime != nil && alertTime.Before(*startTime) {
|
||||
return false
|
||||
}
|
||||
if endTime != nil && alertTime.After(*endTime) {
|
||||
return false
|
||||
}
|
||||
if severity != "" && !strings.EqualFold(alertLevel, severity) {
|
||||
return false
|
||||
}
|
||||
if resourceID != "" && alertResourceID != resourceID {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
trimAlerts := func(alerts []alerts.Alert) []alerts.Alert {
|
||||
if offset > 0 {
|
||||
if offset >= len(alerts) {
|
||||
return alerts[:0]
|
||||
}
|
||||
alerts = alerts[offset:]
|
||||
}
|
||||
if limit > 0 && len(alerts) > limit {
|
||||
alerts = alerts[:limit]
|
||||
}
|
||||
return alerts
|
||||
}
|
||||
|
||||
trimMockAlerts := func(alerts []models.Alert) []models.Alert {
|
||||
if offset > 0 {
|
||||
if offset >= len(alerts) {
|
||||
return alerts[:0]
|
||||
}
|
||||
alerts = alerts[offset:]
|
||||
}
|
||||
if limit > 0 && len(alerts) > limit {
|
||||
alerts = alerts[:limit]
|
||||
}
|
||||
return alerts
|
||||
}
|
||||
|
||||
// Check if mock mode is enabled
|
||||
mockEnabled := mock.IsMockEnabled()
|
||||
log.Info().Bool("mockEnabled", mockEnabled).Msg("GetAlertHistory: checking mock mode")
|
||||
log.Debug().Bool("mockEnabled", mockEnabled).Msg("GetAlertHistory: mock mode status")
|
||||
|
||||
fetchLimit := limit
|
||||
if fetchLimit > 0 && offset > 0 {
|
||||
fetchLimit += offset
|
||||
}
|
||||
|
||||
if mockEnabled {
|
||||
history := mock.GetMockAlertHistory(limit)
|
||||
log.Info().Int("mockHistoryCount", len(history)).Msg("Returning mock alert history")
|
||||
if err := utils.WriteJSONResponse(w, history); err != nil {
|
||||
mockHistory := mock.GetMockAlertHistory(fetchLimit)
|
||||
filtered := make([]models.Alert, 0, len(mockHistory))
|
||||
for _, alert := range mockHistory {
|
||||
if matchesFilters(alert.StartTime, alert.Level, alert.ResourceID) {
|
||||
filtered = append(filtered, alert)
|
||||
}
|
||||
}
|
||||
filtered = trimMockAlerts(filtered)
|
||||
if err := utils.WriteJSONResponse(w, filtered); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write mock alert history response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get real alert history
|
||||
alertHistory := h.monitor.GetAlertManager().GetAlertHistory(limit)
|
||||
if h.monitor == nil {
|
||||
http.Error(w, "monitor is not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.WriteJSONResponse(w, alertHistory); err != nil {
|
||||
manager := h.monitor.GetAlertManager()
|
||||
var history []alerts.Alert
|
||||
if startTime != nil {
|
||||
history = manager.GetAlertHistorySince(*startTime, fetchLimit)
|
||||
} else {
|
||||
history = manager.GetAlertHistory(fetchLimit)
|
||||
}
|
||||
|
||||
filtered := make([]alerts.Alert, 0, len(history))
|
||||
for _, alert := range history {
|
||||
if matchesFilters(alert.StartTime, string(alert.Level), alert.ResourceID) {
|
||||
filtered = append(filtered, alert)
|
||||
}
|
||||
}
|
||||
filtered = trimAlerts(filtered)
|
||||
|
||||
if err := utils.WriteJSONResponse(w, filtered); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write alert history response")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue