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
+50 -6
View File
@@ -438,11 +438,31 @@ func (s *LookupService) parseWHOISOutput(output, domainName string) (*domain.WHO
}
}
// Extract dates
expiryDate := s.parseDate(data["registry_expiry_date"], data["registrar_registration_expiration_date"],
data["expiry_date"], data["expiration_time"], data["expire"], data["paid_until"])
creationDate := s.parseDate(data["creation_date"], data["created_date"], data["registration_time"])
updatedDate := s.parseDate(data["updated_date"], data["last_updated"])
// Extract dates - try many field name variations used by different registries
expiryDate := s.parseDate(
data["registry_expiry_date"],
data["registrar_registration_expiration_date"],
data["expiry_date"],
data["expiration_time"],
data["expire"],
data["paid_until"],
data["expire_date"],
data["renewal_date"],
data["valid_until"],
)
creationDate := s.parseDate(
data["creation_date"],
data["created_date"],
data["registration_time"],
data["registered_on"],
data["domain_registered"],
)
updatedDate := s.parseDate(
data["updated_date"],
data["last_updated"],
data["last_modified"],
data["modified_date"],
)
// Extract registrar - try multiple field names used by different WHOIS servers
registrarName := data["registrar"]
@@ -455,6 +475,15 @@ func (s *LookupService) parseWHOISOutput(output, domainName string) (*domain.WHO
if registrarName == "" {
registrarName = data["registrar_organization"]
}
if registrarName == "" {
registrarName = data["registrant_organization"]
}
if registrarName == "" {
registrarName = data["registrar_url"]
}
if registrarName == "" {
registrarName = data["registrar_abuse_contact_email"]
}
if registrarName == "" {
registrarName = "Unknown"
}
@@ -477,16 +506,31 @@ func (s *LookupService) parseWHOISOutput(output, domainName string) (*domain.WHO
PostalCode: data["registrant_postal_code"],
}
// Try alternate field names for registrant
// Try alternate field names for registrant (.eu uses "holder", other variations)
if registrant.Name == "" {
registrant.Name = data["registrant"]
}
if registrant.Name == "" {
registrant.Name = data["holder"]
}
if registrant.Name == "" {
registrant.Name = data["domain_holder"]
}
if registrant.Organization == "" {
registrant.Organization = data["org"]
}
if registrant.Organization == "" {
registrant.Organization = data["organization"]
}
if registrant.Organization == "" {
registrant.Organization = data["holder_org"]
}
if registrant.Country == "" {
registrant.Country = data["country"]
}
if registrant.Country == "" {
registrant.Country = data["holder_country"]
}
// Parse DNSSEC more thoroughly
dnssec := data["dnssec"]