feat(site): implement subdomain discovery and enhanced monitoring dashboard

This commit introduces a comprehensive subdomain discovery system and significantly upgrades the monitoring and domain management user interfaces.

Key changes include:
- **Subdomain Discovery**: Added a new service in the hub that performs advanced subdomain discovery using DNS brute forcing, Certificate Transparency (CT) log searches, pattern enumeration, and HTTP probing.
- **Enhanced Domain Management**:
    - Added API endpoints for retrieving, discovering, and deleting subdomains.
    - Implemented a new `SubdomainList` component in the UI to manage discovered subdomains.
    - Improved WHOIS lookup robustness by supporting a wider range of registry field variations.
- **Advanced Monitoring UI**:
    - Introduced `GroupedMonitorsTable` to organize monitors by root domain and their respective subdomains.
    - Added visual uptime timelines (heartbeat dots) and response time statistics (Avg, Min, Max, P95, P99) to the monitor detail view.
    - Implemented "Uptime Pills" for high-visibility status indicators in the monitors table.
- **Status Page Management**: Replaced the static status pages table with a full `StatusPageManager` capable of managing status pages and incidents.
- **Refactoring & Cleanup**:
    - Cleaned up `.gitignore` and removed unused reference submodules.
    - Improved domain extraction and grouping logic in the frontend.
    - Enhanced the `SystemsTable` with better sorting and layout.
This commit is contained in:
Tomas Dvorak
2026-05-05 16:14:45 +02:00
parent 21657abe38
commit 7ea9a069f9
22 changed files with 248666 additions and 179 deletions
+41 -1
View File
@@ -52,6 +52,13 @@ func (h *APIHandler) RegisterRoutes(se *core.ServeEvent) {
api.GET("/:id/heartbeats", h.getHeartbeats)
}
// HeartbeatSummary represents a minimal heartbeat for the monitor list
type HeartbeatSummary struct {
Status string `json:"status"`
Ping int `json:"ping"`
Time time.Time `json:"time"`
}
// MonitorResponse represents a monitor in API responses
type MonitorResponse struct {
ID string `json:"id"`
@@ -69,6 +76,7 @@ type MonitorResponse struct {
Description string `json:"description,omitempty"`
LastCheck *time.Time `json:"last_check,omitempty"`
UptimeStats map[string]float64 `json:"uptime_stats,omitempty"`
RecentHeartbeats []HeartbeatSummary `json:"recent_heartbeats,omitempty"`
Tags []string `json:"tags,omitempty"`
Keyword string `json:"keyword,omitempty"`
JSONQuery string `json:"json_query,omitempty"`
@@ -165,6 +173,35 @@ func (h *APIHandler) listMonitors(e *core.RequestEvent) error {
})
}
// getRecentHeartbeats fetches the last N heartbeats for a monitor
func (h *APIHandler) getRecentHeartbeats(monitorID string, limit int) []HeartbeatSummary {
records, err := h.app.FindRecordsByFilter(
"monitor_heartbeats",
"monitor = {:monitorId}",
"-time",
limit,
0,
map[string]any{"monitorId": monitorID},
)
if err != nil {
return nil
}
heartbeats := make([]HeartbeatSummary, 0, len(records))
for _, hb := range records {
var t time.Time
if ts := hb.GetDateTime("time"); !ts.IsZero() {
t = ts.Time()
}
heartbeats = append(heartbeats, HeartbeatSummary{
Status: hb.GetString("status"),
Ping: hb.GetInt("ping"),
Time: t,
})
}
return heartbeats
}
// getMonitor returns a single monitor by ID
func (h *APIHandler) getMonitor(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
@@ -182,7 +219,10 @@ func (h *APIHandler) getMonitor(e *core.RequestEvent) error {
return e.ForbiddenError("Access denied", nil)
}
return e.JSON(http.StatusOK, recordToResponse(record))
resp := recordToResponse(record)
resp.RecentHeartbeats = h.getRecentHeartbeats(id, 12)
return e.JSON(http.StatusOK, resp)
}
// createMonitor creates a new monitor