mirror of
https://github.com/Dvorinka/beszel.git
synced 2026-06-03 21:02:56 +00:00
feat(hub,site): enhance domain management and monitor UI
Build Docker images / Hub (push) Failing after 54s
Build Docker images / Hub (push) Failing after 54s
Implement manual domain expiry overrides, improve subdomain discovery via CT logs, and enhance the monitoring dashboard with favicons and configurable display options. hub: - allow manual expiry and creation date overrides in domain API when WHOIS lookup fails - implement JSON parsing for crt.sh certificate transparency log searches in subdomain discovery - update monitor API routes to use curly brace syntax for path parameters site: - add manual registration date and period inputs to domain dialog - implement monitor favicon support using Google's favicon service - add configurable display options (uptime pills, heartbeat dots) to monitors table - update localization files to include new UI elements
This commit is contained in:
@@ -128,6 +128,9 @@ func (h *APIHandler) createDomain(e *core.RequestEvent) error {
|
||||
NotifyOnSSL bool `json:"notify_on_ssl_expiry"`
|
||||
NotifyOnDNS bool `json:"notify_on_dns_change"`
|
||||
NotifyOnReg bool `json:"notify_on_registrar_change"`
|
||||
// Manual expiry override when WHOIS fails
|
||||
ExpiryDate string `json:"expiry_date,omitempty"`
|
||||
CreationDate string `json:"creation_date,omitempty"`
|
||||
}
|
||||
if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil {
|
||||
return e.BadRequestError("invalid request body", err)
|
||||
@@ -179,6 +182,7 @@ func (h *APIHandler) createDomain(e *core.RequestEvent) error {
|
||||
record.Set("user", authRecord.Id)
|
||||
|
||||
// Auto-lookup if requested
|
||||
lookupHadExpiry := false
|
||||
if req.AutoLookup {
|
||||
lookupSvc := whois.NewLookupService("")
|
||||
ctx := e.Request.Context()
|
||||
@@ -188,6 +192,7 @@ func (h *APIHandler) createDomain(e *core.RequestEvent) error {
|
||||
// Calculate status based on lookup results
|
||||
status := domain.DomainStatusUnknown
|
||||
if domainData.ExpiryDate != nil {
|
||||
lookupHadExpiry = true
|
||||
daysUntil := int(time.Until(*domainData.ExpiryDate).Hours() / 24)
|
||||
if daysUntil < 0 {
|
||||
status = domain.DomainStatusExpired
|
||||
@@ -204,6 +209,29 @@ func (h *APIHandler) createDomain(e *core.RequestEvent) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply manual expiry/creation dates if WHOIS didn't find them
|
||||
if !lookupHadExpiry {
|
||||
if req.ExpiryDate != "" {
|
||||
if t, err := time.Parse("2006-01-02", req.ExpiryDate); err == nil {
|
||||
record.Set("expiry_date", t)
|
||||
// Recalculate status with manual expiry
|
||||
daysUntil := int(time.Until(t).Hours() / 24)
|
||||
status := domain.DomainStatusActive
|
||||
if daysUntil < 0 {
|
||||
status = domain.DomainStatusExpired
|
||||
} else if daysUntil <= req.AlertDaysBefore {
|
||||
status = domain.DomainStatusExpiring
|
||||
}
|
||||
record.Set("status", status)
|
||||
}
|
||||
}
|
||||
if req.CreationDate != "" {
|
||||
if t, err := time.Parse("2006-01-02", req.CreationDate); err == nil {
|
||||
record.Set("creation_date", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.app.Save(record); err != nil {
|
||||
return e.InternalServerError("failed to create domain", err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package domains
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@@ -143,21 +144,73 @@ func (sd *SubdomainDiscovery) dnsBruteForce(ctx context.Context, domainName stri
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// ctLogSearch searches certificate transparency logs
|
||||
// ctLogSearch searches certificate transparency logs via crt.sh
|
||||
func (sd *SubdomainDiscovery) ctLogSearch(ctx context.Context, domainName string, results chan<- DiscoveryResult) {
|
||||
// Query crt.sh for certificates
|
||||
url := fmt.Sprintf("https://crt.sh/?q=%%.%s&output=json", domainName)
|
||||
|
||||
resp, err := sd.client.Get(url)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
log.Printf("[subdomain-discovery] CT log search failed for %s: %v", domainName, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := sd.client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[subdomain-discovery] CT log search failed for %s: %v", domainName, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Parse response (simplified - in production would parse JSON)
|
||||
// For now, just log that we attempted this
|
||||
log.Printf("[subdomain-discovery] CT log search attempted for %s (status: %d)", domainName, resp.StatusCode)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("[subdomain-discovery] CT log search returned status %d for %s", resp.StatusCode, domainName)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse crt.sh JSON response
|
||||
var entries []struct {
|
||||
NameValue string `json:"name_value"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&entries); err != nil {
|
||||
log.Printf("[subdomain-discovery] Failed to parse CT log response for %s: %v", domainName, err)
|
||||
return
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
for _, entry := range entries {
|
||||
// crt.sh returns one name_value per line, may contain wildcards or multiple names
|
||||
names := strings.Split(entry.NameValue, "\n")
|
||||
for _, name := range names {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" || name == domainName {
|
||||
continue
|
||||
}
|
||||
// Remove wildcard prefix
|
||||
name = strings.TrimPrefix(name, "*.")
|
||||
// Only include subdomains of the target domain
|
||||
if !strings.HasSuffix(name, "."+domainName) {
|
||||
continue
|
||||
}
|
||||
subdomain := strings.TrimSuffix(name, "."+domainName)
|
||||
if subdomain == "" || seen[subdomain] {
|
||||
continue
|
||||
}
|
||||
seen[subdomain] = true
|
||||
|
||||
// Try to resolve IPs
|
||||
ips, _ := net.LookupHost(name)
|
||||
|
||||
results <- DiscoveryResult{
|
||||
Subdomain: subdomain,
|
||||
FullDomain: name,
|
||||
IPAddresses: ips,
|
||||
Source: "certificate",
|
||||
FoundAt: time.Now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[subdomain-discovery] CT log search found %d unique subdomains for %s", len(seen), domainName)
|
||||
}
|
||||
|
||||
// patternEnumeration enumerates common subdomain patterns
|
||||
|
||||
Reference in New Issue
Block a user